Differences

This shows you the differences between two versions of the page.

Link to this comparison view

ac:laboratoare:07 [2024/11/07 12:50]
dimitrie.valu
ac:laboratoare:07 [2024/11/14 13:05] (current)
dimitrie.valu
Line 1: Line 1:
 ===== Lab 07 - Whatsapp End-to-end Encryption ===== ===== Lab 07 - Whatsapp End-to-end Encryption =====
  
-In this lab you will implement a simplified version of The Signal Protocol, which is the basis for WhatsApp'​s end-to-end encryption.+In this lab you will implement a simplified version of the Signal Protocol, which is the basis for WhatsApp'​s end-to-end encryption.
  
 The first versions of Whatsapp protocol were described [[https://​cryptome.org/​2016/​04/​whatsapp-crypto.pdf|here]]. A more recent document is available [[https://​www.whatsapp.com/​security/​WhatsApp-Security-Whitepaper.pdf|here]]. The first versions of Whatsapp protocol were described [[https://​cryptome.org/​2016/​04/​whatsapp-crypto.pdf|here]]. A more recent document is available [[https://​www.whatsapp.com/​security/​WhatsApp-Security-Whitepaper.pdf|here]].
Line 9: Line 9:
 For the Elliptic Curves, you can use [[https://​github.com/​Muterra/​donna25519|this]] library. For the Elliptic Curves, you can use [[https://​github.com/​Muterra/​donna25519|this]] library.
  
-For installation,​ follow these steps: +For installation,​ follow these steps (NOTE: **you can use your ''​%%fep%%''​ instance via Python3 environments**): 
-  ​[If in the lab] Log-in as admin (need admin user/​password from lab host) +  * Install the necessary tools (not necessary on ''​%%fep%%''​): 
-  * [If in the lab] Select Administration->​Software Sources and then select the romanian repos for high bandwidth +<​code>​ 
-  * Install the necessary tools: sudo apt-get install build-essential ​setuptools python-dev +sudo apt install build-essential ​python3-dev 
-  * Install pip:  ​sudo apt-get install pip +sudo apt install ​python3-pip 
-  * Install donna via pip: sudo pip install donna25519 +</​code>​ 
-  * Log-out from admin account +  * Use ''​%%wget%%''​ to download the required zip (find it below) 
-  * Log-in with student as usual+  * Create a Python3 environment,​ make sure PyPI is up to date and install the required packages: 
 +<​code>​ 
 +python3 ​-m venv create env 
 +source ./​env/​bin/​activate 
 +pip install ​--upgrade pip 
 +pip install cryptography donna25519 
 +</​code>​
  
-=== Task === +**If local installation does not work, use your ''​%%fep%%''​ instance.** 
-Create a common master_secret for two clients which communicate through a server. (TODO 1.1 & TODO 1.2)+ 
 +=== Task === 
 +Find the required zip here - {{:​ac:​laboratoare:​lab07.zip|}}. 
 + 
 +Create a common ​''​%%master_secret%%'' ​for two clients which communicate through a server. (**TODO 1.1** **TODO 1.2**)
 Print it on both clients and make sure they both have the same secret. Print it on both clients and make sure they both have the same secret.
  
Line 25: Line 35:
 Open three different terminals. Open three different terminals.
  
-First terminal:+First terminal ​(start the server):
 <​code>​python main_server.py</​code>​ <​code>​python main_server.py</​code>​
  
-Second terminal: +Second terminal ​(start the first client and enter ''​%%RECV%%''​ mode
-<code>​python main_client.py</​code> +<​code>​ 
- +python main_client.py 
-Third terminal: +RECV
-<​code>​python main_client.py +
-MSG <​id_other_client>​ Hello!</​code>​ +
- +
-Second terminal: +
-<​code>​RECV</​code>​ +
- +
-<code python '​main_client.py'>​ +
-from wa_client import Client +
-from wa_server import Server +
- +
-SERVER_IP = '​127.0.0.1'​ +
-SERVER_PORT = 7778 +
- +
-if __name__ == '​__main__':​ +
- c = Client(SERVER_IP,​ SERVER_PORT) +
- while True: +
- cmd = input('​MSG <​user_id>​ <​message>​\n'​) +
- cmd = cmd.split("​ ") +
- if cmd[0] == "​MSG":​ +
- user_id = int(cmd[1]) +
- msg = " "​.join(cmd[2:​]) +
- c.send_message(user_id,​ msg) +
- elif cmd[0] == "​RECV":​ +
- msg = c.recv_message() +
- print(msg)+
 </​code>​ </​code>​
  
-<code python ​'​main_server.py'> +Third terminal (start the second client and send a message): 
-from wa_client import Client +<code
-from wa_server import Server +python ​main_client.py 
- +MSG <​id_other_clientHello!
-SERVER_PORT = 7778 +
- +
-if __name__ == '​__main__':​ +
- s = Server(SERVER_PORT) +
- s.start()+
 </​code>​ </​code>​
  
-<code python '​wa_client.py'>​ 
-from donna25519 import * 
-import os 
-from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 
-import struct 
-import socket 
-import hashlib 
-import hmac 
-from math import ceil 
- 
-O_NUM = 10 # number of initial One-Time Pre Keys 
- 
-hash_len = 32 
-def hmac_sha256(key,​ data): 
-    return hmac.new(key,​ data, hashlib.sha256).digest() 
- 
-def hkdf(length,​ ikm, salt = b""​):​ 
-    """​Parameters:​ 
-    length: output length in bytes 
-    ikm: input key material 
-    """​ 
-    prk = hmac_sha256(salt,​ ikm) 
-    t = b""​ 
-    okm = b""​ 
-    for i in range(int(ceil(1.0 * length / hash_len))):​ 
-        t = hmac_sha256(prk,​ t + bytes([1+i])) 
-        okm += t 
-    return okm[:​length] 
- 
-class ClientSession:​ 
-    def __init__(self,​ root_key, chain_key_s,​ chain_key_r,​ eph_own, eph_peer): 
-        self.root_key = root_key 
-        self.chain_key_s = chain_key_s 
-        self.chain_key_r = chain_key_r 
-        self.eph_own = eph_own 
-        self.eph_peer = eph_peer 
- 
-    def update_keys(self,​ eph_peer): 
-        """​Performs vertical ratcheting 
-        Updates root & chain keys to match peer's (for decrypting its messages) 
-        Then updates root & chain keys again with fresh ephemereal key (for encrypting 
-        my messages) 
-        """​ 
-        self.eph_peer = eph_peer 
-        # Update RootKey & receiving ChainKey 
-        # TODO 2 Obtain new root_key and chain_key_r from hkdf over 
-        # DH(eph_own, eph_peer) and root_key (can be used as salt parameter in hkdf) 
-        self.root_key = ""​ 
-        self.chain_key_r = ""​ 
- 
-        # Update RootKey & sending ChainKey 
-        self.eph_own = None # TODO 2 Generate new ephemereal key pair 
-        # TODO 2 Obtain new root_key and chain_key_s from hkdf over 
-        # DH(eph_own, eph_peer) and root_key (can be used as salt parameter in hkdf) 
-        self.root_key = ""​ 
-        self.chain_key_s = ""​ 
- 
-class Client: 
-    def __init__(self,​ server_ip, server_port):​ 
-        self.I = PrivateKey() # Identity Key Pair 
-        self.S = PrivateKey() # Signed Pre Key 
-        self.O_queue = [PrivateKey() for i in range(O_NUM)] # One-Time Pre Keys 
- 
-        self.s = socket.socket(socket.AF_INET,​ socket.SOCK_STREAM) 
-        self.s.connect((server_ip,​ server_port)) 
-        self.user_id = self.register() 
- 
-        # self.existing_sessions[user_id] = session with that user (ClientSession) 
-        self.existing_sessions = {} # initially no existing session 
- 
-    def register(self):​ 
-        """​Lets the server know about its presence"""​ 
-        # TODO 1.1 send public keys of I, S and the list of O to the server 
-        # e.g. self.s.send(self.I.get_public().public) 
-        # You may also print a log message, e.g. print "Sent I = " + self.I.get_public().public.hex() 
- 
-        # You can send the list of O by first sending its length and then the keys one by one 
-        # use struct.pack('​!i',​ my_int) to send 4 byte signed integers 
- 
-        # receive your user id from the server (might need later) 
-        # use struct.unpack('​!i',​ received_int)[0] to get an integer 
-        # e.g. struct.unpack('​!i',​ self.s.recv(4))[0] 
- 
-        user_id = 0 # TODO 1.1 change with received user id 
-        print("​Got User ID = %d" % user_id) 
-        return user_id 
- 
-    def get_send_message_key(self,​ peer_user_id):​ 
-        """​Gets sending MessageKey using ChainKey from peer_user_id'​s ClientSession"""​ 
-        session = self.existing_sessions[peer_user_id] 
- 
-        # TODO 2 Generate message_key from chain_key_s (session.chain_key_s) 
-        # message_key = HMAC_SHA256(ChainKey,​ 0x01) 
-        message_key = None 
- 
-        # TODO 2 Update chain_key_s 
-        # ChainKey = HMAC_SHA256(ChainKey,​ 0x02) 
-        session.chain_key_s = None 
- 
-        return message_key 
- 
-    def get_recv_message_key(self,​ peer_user_id):​ 
-        """​Gets MessageKey using ChainKey from peer_user_id'​s ClientSession"""​ 
-        session = self.existing_sessions[peer_user_id] 
- 
-        # TODO 2 Generate message_key from chain_key_r (session.chain_key_r) 
-        # message_key = HMAC_SHA256(ChainKey,​ 0x01) 
-        message_key = None 
- 
-        # TODO 2 Update chain_key_r 
-        # ChainKey = HMAC_SHA256(ChainKey,​ 0x02) 
-        session.chain_key = None 
- 
-        return message_key 
- 
-    def pad_message(self,​ message): 
-        pad_number = (-len(message)) % 16 
-        if pad_number == 0: 
-            pad_number += 16 
- 
-        message += ''​.join([chr(pad_number) for i in xrange(pad_number)]) 
-        return message 
- 
-    def unpad_message(self,​ message): 
-        pad_number = ord(message[-1]) 
-        message = message[:​-pad_number] 
-        return message 
- 
-    def send_message(self,​ to_user_id, message): 
-        """​Sends a message to user with id <​to_user_id>​ 
-        If there is no existing ClientSession between them, they create one 
-        """​ 
-        if to_user_id not in self.existing_sessions:​ 
-            self.setup_session(to_user_id) 
- 
-        self.s.send(b"​SEND"​) 
-        self.s.send(struct.pack('​!i',​ to_user_id)) 
- 
-        # Get MessageKey for next message 
-        message_key = self.get_send_message_key(to_user_id) 
- 
-        # Encrypt message using MessageKey 
-        iv = os.urandom(16) 
-        cipher = Cipher(algorithms.AES(message_key),​ modes.CBC(iv)) 
-        encryptor = cipher.encryptor() 
-        message = self.pad_message(message) 
-        enc_message = encryptor.update(message) + encryptor.finalize() 
- 
-        # TODO 2 Send own ephemereal public key (get it from the ClientSession with to_user_id) 
-        session = self.existing_sessions[to_user_id] 
-        raw_eph_own = session.eph_own.get_public().public 
- 
-        # TODO 2 Send encrypted message length to server 
- 
-        # TODO 2 Send encrypted message to server 
- 
-    def recv_message(self):​ 
-        """​Receives a message 
-        It can be either from a new session exchange (tag=NEWS), or an actual message(tag=MESG)"""​ 
-        tag = self.s.recv(4) 
-        if tag == "​NEWS":​ # NEW Session 
-            ini_user_id = struct.unpack('​!i',​ self.s.recv(4))[0] 
-            self.setup_session_rec(ini_user_id) 
-            tag = self.s.recv(4) 
- 
-        if tag == "​MESG":​ # message incoming 
-            # Get sender user id 
-            sender_user_id = struct.unpack('​!i',​ self.s.recv(4))[0] 
- 
-            # Get ephemereal key 
-            raw_eph_peer = self.s.recv(32) 
- 
- 
-            session = self.existing_sessions[sender_user_id] 
-            # TOD2 3 If received eph_peer is unknown (different than the one stored in the 
-            # ClientSession),​ update the keys 
-            # The first time current eph_peer will be None, so make sure you update in that 
-            # case also 
-            if False: # change with actual condition 
-                eph_peer = PublicKey(raw_eph_peer) 
-                session.update_keys(eph_peer) 
- 
-            # Get message length 
-            mlen = struct.unpack('​!i',​ self.s.recv(4))[0] 
- 
-            # Get message 
-            enc_message = self.s.recv(mlen) 
- 
-            # Decrypt message using MessageKey 
-            message_key = self.get_recv_message_key(sender_user_id) 
-            iv = enc_message[:​16] 
-            enc_message = enc_message[16:​] 
-            cipher = Cipher(algorithms.AES(message_key),​ modes.CBC(iv)) 
-            decryptor = cipher.decryptor() 
-            msg = decryptor.update(enc_message) + decryptor.finalize() 
-            msg = self.unpad_message(msg) 
- 
-            return msg 
- 
-    def record_new_client_session(self,​ master_secret,​ client_id, eph_peer = None): 
-        """​Forges RootKey and ChainKey from master_secret and stores them in a ClientSession 
-        which is then put in the existing_sessions dictionary"""​ 
- 
-        root_key = master_secret # master_secret is considered as the base root key 
-        if eph_peer: # initiator considers Srec as initial peer ephemereal key 
-            eph_own = PrivateKey() # Generate new send ephemereal key 
- 
-            # TODO 2 Get root_key and chain_key_s with hkdf over DH(eph_own, eph_peer) and 
-            # master secret 
-            root_key = ""​ 
-            chain_key_s = ""​ 
-            chain_key_r = None # Will initialize with first received message 
-        else: # the non-initiator (first receiver) will enter this branch 
-            eph_own = self.S # initiator considers receiver'​s S as first "​ephemereal"​ key 
-            chain_key_s = None 
-            chain_key_r = None 
- 
-        new_client_session = ClientSession(root_key,​ chain_key_s,​ chain_key_r,​ eph_own, eph_peer) 
-        self.existing_sessions[client_id] = new_client_session 
- 
-    def setup_session(self,​ to_user_id):​ 
-        """​Initiates a new session with to_user_id"""​ 
-        self.s.send(b"​GENS"​) # GENerate Session command 
-        self.s.send(struct.pack('​!i',​ to_user_id)) 
-        resp = self.s.recv(4) 
-        if resp == "​FAIL":​ 
-            print("​User does not exist"​) 
-            return False 
- 
-        # TODO 1.1 Get Irec (done), Srec, Orec from server 
-        Irec = PublicKey(self.s.recv(32)) 
-        # Srec = ... 
-        # Prec = ... 
- 
-        # TODO 1.1 Generate Eini (ephemeral private key of initiator) 
-        # Eini = ... (see above for function to retrieve ephemeral private key) 
- 
-        # TODO 1.1 Compute master_secret from DH(Iini, Srec), DH(Eini, Irec), DH(Eini, Srec), DH(Eini, Orec) 
-        # See method do_exchange in donna library: https://​github.com/​Muterra/​donna25519 
-        # Use '​+'​ to concatenate output of various DH exchanges 
-        master_secret = ""​ 
-        print(master_secret.hex()) 
- 
-        # TODO 1.2 Send Eini to the other client (through server) 
- 
-        # Generate new ClientSession using master_secret 
-        self.record_new_client_session(master_secret,​ to_user_id, Srec) 
- 
-    def setup_session_rec(self,​ from_user_id):​ 
-        """​Receives a new session with from_user_id"""​ 
- 
-        # TODO 1.2 Receive initiator'​s public keys Eini, Iini and Orec_pub 
-        Eini = PublicKey(self.s.recv(32)) 
-        # Iini = ... 
-        # Orec_pub = ... 
- 
-        # TODO 1.2 Get private Orec from my O_queue based on received public Orec and then remove from O_queue 
- 
-        # TODO 1.2 Compute master_secret from DH(Iini, Srec), DH(Eini, Irec), DH(Eini, Srec), DH(Eini, Orec) 
-        # See above, in method setup_session 
-        master_secret = ""​ 
-        print(master_secret.hex()) 
- 
-        # Generate new ClientSession using master_secret 
-        self.record_new_client_session(master_secret,​ from_user_id) 
-</​code>​ 
- 
-<code python '​wa_server.py'>​ 
-from donna25519 import * 
-import socket 
-import threading 
-import struct 
- 
-TCP_IP = '​127.0.0.1'​ 
-MAX_CLIENTS = 10 
- 
-class ClientRecord:​ 
-    """​Structure for keeping record of a client'​s public keys"""​ 
-    def __init__(self,​ I, S, O_queue, clientsocket):​ 
-        self.I = I 
-        self.S = S 
-        self.O_queue = O_queue 
-        self.s = clientsocket 
- 
-class Server: 
-    def __init__(self,​ port): 
-        self.port = port 
-        self.next_id = 1 
- 
-        self.registered_clients = {} 
- 
-    def start(self):​ 
-        """​Start listening to client connections"""​ 
-        print("​Starting server on port %d" % self.port) 
-        s = socket.socket(socket.AF_INET,​ socket.SOCK_STREAM) 
-        s.bind((TCP_IP,​ self.port)) 
-        s.listen(MAX_CLIENTS) 
-        while True: 
-            c, addr = s.accept() # connection with new client 
-            print("​New client from address " + str(addr)) 
-            threading.Thread(target = self.on_new_client,​ args = (c, addr)).start() 
- 
-        s.close() 
-        ​ 
-    def on_new_client(self,​ clientsocket,​ addr): 
-        """​Runs when a new client connects to the server"""​ 
-        I = PublicKey(clientsocket.recv(32)) 
-        print("​Received I = " + I.public.hex()) 
-        S = PublicKey(clientsocket.recv(32)) 
-        print("​Received S = " + S.public.hex()) 
-        o_num = struct.unpack('​!i',​ clientsocket.recv(4))[0] 
-        O_queue = [] 
-        for i in xrange(o_num):​ 
-            O_queue.append(PublicKey(clientsocket.recv(32))) 
-            print("​Received O = " + O_queue[i].public.hex()) 
- 
-        user_id = self.register_client(I,​ S, O_queue, clientsocket) 
-        clientsocket.send(struct.pack('​!i',​ user_id)) 
- 
-        while True: 
-            cmd = clientsocket.recv(4) 
-            if cmd == "​GENS":​ # GENerate Session 
-                raw_id = clientsocket.recv(4) 
-                rec_user_id = struct.unpack('​!i',​ raw_id)[0] 
-                if rec_user_id not in self.registered_clients:​ 
-                    clientsocket.send("​FAIL"​) 
-                    continue 
-                clientsocket.send(b"​GOOD"​) 
-                # Send Irec, Srec, Orec to initiator 
-                Irec = self.registered_clients[rec_user_id].I 
-                Srec = self.registered_clients[rec_user_id].S 
-                Orec = self.registered_clients[rec_user_id].O_queue.pop() 
-                clientsocket.send(Irec.public) 
-                clientsocket.send(Srec.public) 
-                clientsocket.send(Orec.public) 
- 
-                # get Eini from initiator 
-                Eini = PublicKey(clientsocket.recv(32)) 
- 
-                rec_clientsocket = self.registered_clients[rec_user_id].s 
-                rec_clientsocket.send(b"​NEWS"​) # NEW Session 
-                rec_clientsocket.send(struct.pack('​!i',​ user_id)) 
-                # Send Eini, Iini, Orec to recipient 
-                rec_clientsocket.send(Eini.public) 
-                rec_clientsocket.send(I.public) 
-                rec_clientsocket.send(Orec.public) 
-            if cmd == "​SEND":​ # SEND Message 
-                # Get recipient used id 
-                raw_id = clientsocket.recv(4) 
-                rec_user_id = struct.unpack('​!i',​ raw_id)[0] 
- 
-                rec_clientsocket = self.registered_clients[rec_user_id].s 
-                rec_clientsocket.send(b"​MESG"​) # send message tag 
- 
-                # Send sender user id 
-                rec_clientsocket.send(struct.pack('​!i',​ user_id)) 
- 
-                # Forward sender ephemereal key 
-                eph_s = clientsocket.recv(32) 
-                rec_clientsocket.send(eph_s) 
- 
-                # Forward message length 
-                raw_mlen = clientsocket.recv(4) 
-                rec_clientsocket.send(raw_mlen) 
- 
-                # Forward message 
-                mlen = struct.unpack('​!i',​ raw_mlen)[0] 
-                raw_msg = clientsocket.recv(mlen) 
-                rec_clientsocket.send(raw_msg) 
- 
-    def register_client(self,​ I, S, O_queue, clientsocket):​ 
-        """​Registers new client'​s I, S, and O_queue and returns the new client'​s id"""​ 
-        new_client = ClientRecord(I,​ S, O_queue, clientsocket) 
- 
-        user_id = self.next_id 
-        self.next_id += 1 
- 
-        self.registered_clients[user_id] = new_client 
- 
-        return user_id ​               
-</​code>​ 
ac/laboratoare/07.1730976623.txt.gz · Last modified: 2024/11/07 12:50 by dimitrie.valu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0