def hkdf(ikm, l=16, salt=b"0" * 32, info=b""): """ HMAC-based Extract-and-Expand Key Derivation Function (HKDF) https://tools.ietf.org/html/rfc5869 ARGS: ikm : input keying material l : length of output keying material in octets salt : (optional) salt value (defaults to 32 0s) info : (optional) context and application specific information (defaults to null string) Returns: okm : output key material """ hash_len = 32 extractor = Hmac(b"sha256", salt) extractor.update(ikm) prk = extractor.digest() t = b"" okm = b"" for i in range(int(ceil(float(l) / hash_len))): expander = Hmac(b"sha256", prk) expander.update(t + info + bytes([1 + i])) t = expander.digest() okm += t return okm[:l]
def mix_server_one_hop(private_key, message_list): """ Implements the decoding for a simple one-hop mix. Each message is decoded in turn: - A shared key is derived from the message public key and the mix private_key. - the hmac is checked against all encrypted parts of the message - the address and message are decrypted, decoded and returned """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not len(msg.hmac) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Check the HMAC h = Hmac(b"sha512", hmac_key) h.update(msg.address) h.update(msg.message) expected_mac = h.digest() print "my hmac: " + str(msg.hmac) print "ex hmac: " + str(expected_mac[:20]) if not secure_compare(msg.hmac, expected_mac[:20]): raise Exception("HMAC check failure") ## Decrypt the address and the message iv = b"\x00"*16 address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) output = (address_full[:address_len], message_full[:message_len]) out_queue += [output] return sorted(out_queue)
def mix_server_one_hop(private_key, message_list): """ Implements the decoding for a simple one-hop mix. Each message is decoded in turn: - A shared key is derived from the message public key and the mix private_key. - the hmac is checked against all encrypted parts of the message - the address and message are decrypted, decoded and returned """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not len(msg.hmac) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Check the HMAC h = Hmac(b"sha512", hmac_key) h.update(msg.address) h.update(msg.message) expected_mac = h.digest()[:20] if not secure_compare(msg.hmac, expected_mac[:20]): raise Exception("HMAC check failure") ## Decrypt the address and the message iv = b"\x00" * 16 # Why are we using an all zero IV?! # iv = urandom(16) address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) output = (address_full[:address_len], message_full[:message_len]) out_queue += [output] return sorted(out_queue)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE # First get a shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Encrypt the address and the message iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) ## Form the HMAC h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] assert len(address_cipher) == 258 assert len(message_cipher) == 1002 assert len(expected_mac) == 20 return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # generate shared element shared = public_key.pt_mul(private_key) material = sha512(shared.export()).digest() # split shared key for different operations hmac_key = material[:16] address_key = material[16:32] message_key = material[32:48] # random inistialisation vector iv = b"\x00" * 16 # encoding address and message address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) # generate hmac h h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() shared_element = public_key.pt_mul(private_key) key_material = sha512(shared_element.export()).digest() hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## ADD CODE HERE # TODO: # - Encode message to be processed by mix_server_one_hop # client_public_key is an EC point # expected_hmac is the hmac of address_cipher and message_cipher # address_cipher and message_cipher are AES-CTR ciphertexts # - Make sure mix_server_one_hop decodes message correctly iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # generate shared element shared = public_key.pt_mul(private_key) material = sha512(shared.export()).digest() # split shared key for different operations hmac_key = material[:16] address_key = material[16:32] message_key = material[32:48] # random inistialisation vector iv = b"\x00"*16 # encoding address and message address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) # generate hmac h h = Hmac(b"sha512",hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_one_hop(public_key, address, message): """ Encode a message to travel through a single mix with a set public key. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'OneHopMixMessage' with four parts: a public key, an hmac (20 bytes), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # Use those as the payload for encryption address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() encryption_key = private_key * public_key ek = sha512(encryption_key.export()).digest() #TODO ADD CODE HERE #get coresponding key parts hmac_key = ek[:16] address_key = ek[16:32] message_key = ek[32:48] #encrypt iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) #calculate mac h = Hmac(b"sha512", hmac_key) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] return OneHopMixMessage(client_public_key, expected_mac, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE key_materials = [] # First go through the list in the regular order # in order to extract the key material. # It can be done only in this direction because # of the blinding factor for public_key in public_keys: # First get a shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() key_materials.insert(0, key_material) # Extract a blinding factor for the public_key blinding_factor = Bn.from_binary(key_material[48:]) private_key = blinding_factor * private_key address_cipher = address_plaintext message_cipher = message_plaintext hmacs = [] # Then we can go in the reverse direction # to encapulate the message as many times as necessary for j, public_key in enumerate(reversed(public_keys)): key_material = key_materials[j] # this is already in reverse # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Encrypt address & message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Encrypt hmacs new_hmacs = [] for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_ciphertext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_ciphertext] hmacs = new_hmacs # Calculate the HMAC h = Hmac(b"sha512", hmac_key) for other_mac in hmacs: h.update(other_mac) h.update(address_cipher) h.update(message_cipher) hmac = h.digest() hmacs.insert(0, hmac[:20]) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # initilise lists for hmacs and blinding_keys hmacs = [] blinding_keys = [] # initilise variables address_cipher = address_plaintext message_cipher = message_plaintext # initial blinding_factor set to 1 blinding_factor = Bn(1) # append first public key to list blinding_keys.append(public_keys[0]) length = len(public_keys) for i in range(1, length): # generate shared element shared = private_key * blinding_keys[-1] material = sha512(shared.export()).digest() # generate another bliding factor blinding_factor = blinding_factor * Bn.from_binary(material[48:]) # calculate and append blinding factor to a list public_key = public_keys[i] blinding_keys = blinding_keys + [blinding_factor * public_key] # In reverse order calculate message for each hop using generated blinding factors blinding_keys = reversed(blinding_keys) for key in blinding_keys: # generate shared element shared = private_key * key material = sha512(shared.export()).digest() # split shared key for different operations hmac_key = material[:16] address_key = material[16:32] message_key = material[32:48] # random inistialisation vector iv = b"\x00"*16 # encoding address and message address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # temporary list of hmacs temp_hmacs = [] h = Hmac(b"sha512", hmac_key) for i in range(0,len(hmacs)): prev_mac = hmacs[i] # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00"*14) # encode hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, prev_mac) # add the encoded hmac to the temporary list temp_hmacs = temp_hmacs + [hmac_plaintext] h.update(hmac_plaintext) h.update(address_cipher) h.update(message_cipher) # turn h into binary expected_mac = h.digest() # take first 20 bits expected_mac = expected_mac[:20] # insert expected_mac (final mac) to the beggining of the list temp_hmacs = [expected_mac] + temp_hmacs # update hmacs list with temp_hmacs list hmacs = temp_hmacs return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() iv = b"\x00" * 16 # caluculate the blinding factor of all the # hops, can't be done during hmacs as it's in a different order. # blind_factors = [] shared_keys = [] for i, k in enumerate(public_keys): if i == 0: blind_factors.append(1) else: pub_key = public_keys[i - 1] # The shared key that the hop will use to calculate the factor shared_key = shared_keys[i - 1] key_digest = sha512(shared_key).digest() # the blinding factor that they will use blinding_factor = Bn.from_binary(key_digest[48:]) blind_factors.append(blinding_factor) shared_key = public_keys[i].pt_mul(private_key) for j, fac in enumerate(blind_factors[0:i + 1]): shared_key = shared_key.pt_mul(blind_factors[j]) shared_key = shared_key.export() shared_keys.append(shared_key) ## Reverse the key lists, since we will compute hmacs in reverse order shared_keys.reverse() public_keys.reverse() message_ciphers = [] address_ciphers = [] hmacs = [] previous_hmac_key = None for i, pub in enumerate(public_keys): # Get shared key from precomputed list shared_key = shared_keys[i] key_digest = sha512(shared_key).digest() address_key = key_digest[16:32] message_key = key_digest[32:48] hmac_key = key_digest[:16] ## 1. Encrypt The Message if i == 0: message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) else: message_cipher = aes_ctr_enc_dec(message_key, iv, message_ciphers[i - 1]) address_cipher = aes_ctr_enc_dec(address_key, iv, address_ciphers[i - 1]) message_ciphers.append(message_cipher) address_ciphers.append(address_cipher) ## 2. Encrypt the old HMACs for q, mac in enumerate(hmacs): iv = pack("H14s", len(hmacs) - q - 1, b"\x00" * 14) hmacs[q] = aes_ctr_enc_dec(hmac_key, iv, mac) previous_hmac_key = hmac_key ## 3. Compute the new HMAC h = Hmac(b"sha512", hmac_key) ## Iterate backwards through the previous hmacs for old_mac in hmacs[::-1]: h.update(old_mac) h.update(address_ciphers[i]) h.update(message_ciphers[i]) new_mac = h.digest() hmacs.append(new_mac[:20]) # Hmacs were built in reverse order, put them back in order hmacs.reverse() return NHopMixMessage(client_public_key, hmacs, address_ciphers[len(address_ciphers) - 1], message_ciphers[len(message_ciphers) - 1])
def mix_server_n_hop(private_key, message_list, final=False): """ Decodes a NHopMixMessage message and outputs either messages destined to the next mix or a list of tuples (address, message) (if final=True) to be sent to their final recipients. Broadly speaking the mix will process each message in turn: - it derives a shared key (using its private_key), - checks the first hmac, - decrypts all other parts, - either forwards or decodes the message. """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not isinstance(msg.hmacs, list) or \ not len(msg.hmacs[0]) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Extract a blinding factor for the public_key blinding_factor = Bn.from_binary(key_material[48:]) new_ec_public_key = blinding_factor * msg.ec_public_key ## Check the HMAC h = Hmac(b"sha512", hmac_key) for other_mac in msg.hmacs[1:]: h.update(other_mac) h.update(msg.address) h.update(msg.message) expected_mac = h.digest() if not secure_compare(msg.hmacs[0], expected_mac[:20]): raise Exception("HMAC check failure") ## Decrypt the hmacs, address and the message aes = Cipher("AES-128-CTR") # Decrypt hmacs new_hmacs = [] for i, other_mac in enumerate(msg.hmacs[1:]): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_plaintext] # Decrypt address & message iv = b"\x00" * 16 address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) if final: # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) out_msg = (address_full[:address_len], message_full[:message_len]) out_queue += [out_msg] else: # Pass the new mix message to the next mix out_msg = NHopMixMessage(new_ec_public_key, new_hmacs, address_plaintext, message_plaintext) out_queue += [out_msg] return out_queue
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! # ! = Network byte order, H = unsigned short, 256s = string 256 chars long address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE key_materials = [] private_keys = [private_key] ## Create the shared elements and key materials for each of the hops, ## by iterating through the list of public keys provided for i, hop_public_key in enumerate(public_keys): ## First get a shared key for each mix shared_element = private_keys[i] * hop_public_key key_material = sha512(shared_element.export()).digest() key_materials.append(key_material) ## Extract a blinding factor for this key material blinding_factor = Bn.from_binary(key_material[48:]) ## Get a new private key using the blinding factor for the next mix new_ec_private_key = blinding_factor * private_keys[i] private_keys.append(new_ec_private_key) ## Initialization address_cipher = address_plaintext message_cipher = message_plaintext hmacs = [] ## This iteration needs to happen in reverse, as we need to "encapsulate" ## the information for the last hop in the info for the second to last etc. counter = 0 for i, key_material in reversed(list(enumerate(key_materials))): ## Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Encrypt the address and the message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) ## Create HMAC h = Hmac(b"sha512", hmac_key) print(len(key_materials) - i - 1) ## Encrypt other mac for the mixes after the current one for j, other_mac in enumerate(hmacs[:counter]): ## iv different for each mac iv = pack("H14s", j, b"\x00" * 14) hmac_cipher = aes_ctr_enc_dec(hmac_key, iv, other_mac) ## Add this cipher to the HMAC for the current mix h.update(hmac_cipher) ## Replace the HMAC for the mixes after the current one by their cipher hmacs.pop(j) hmacs.insert(j, hmac_cipher) ## Add the address and message cipher to the HMAC h.update(address_cipher) h.update(message_cipher) ## Insert the HMAC at the list top expected_mac = h.digest()[:20] hmacs.insert(0, expected_mac) counter += 1 print('Client: ', hmacs) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) # Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() hmacs = [] blinding_factor = Bn(1) new_pub_keys = [] # Get a list of new blinded public keys counter = 0 for key in public_keys: if counter == 0: new_pub_keys.append(key) else: shared_element = private_key * new_pub_keys[-1] shared_key = sha512(shared_element.export()).digest() blinding_factor *= Bn.from_binary(shared_key[48:]) new_key = blinding_factor * key new_pub_keys.append(new_key) counter += 1 #reverse the list as you start encrypting from last hop new_pub_keys.reverse() address_cipher = address_plaintext message_cipher = message_plaintext for pub_key in new_pub_keys: # Generate Keying Material new_shared_material = private_key * pub_key new_shared_key = sha512(new_shared_material.export()).digest() # Calculate Other Keys hmac_key = new_shared_key[:16] address_key = new_shared_key[16:32] message_key = new_shared_key[32:48] iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) decrypted_hmacs = [] h = Hmac(b"sha512", hmac_key) # Decrypt HMACs and update them for i,other_hmac in enumerate(hmacs): hmac_iv = pack("H14s", i, b"\x00"*14) decrypted_hmac = aes_ctr_enc_dec(hmac_key, hmac_iv, other_hmac) h.update(decrypted_hmac) decrypted_hmacs.append(decrypted_hmac) # Update HMAC of message and cipher h.update(address_cipher) h.update(message_cipher) expected_hmac = h.digest()[:20] # Add new HMACs to list hmacs = [expected_hmac] + decrypted_hmacs ## ADD CODE HERE return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_cipher = pack("!H256s", len(address), address) message_cipher = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() hmacs = [] key_materials = [] """ We need to precompute the private keys that we're going to be use considering the Blinding Factor. Encryption is back to front but Blinding factor is front to back, that's why we need to do this in advance. """ for pk_i, public_key in enumerate(public_keys): shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() key_materials.append(key_material) # Apply blinding factor to private key for the next shared key derivation blinding_factor = Bn.from_binary(key_material[48:]) private_key *= blinding_factor """ Iterate list back to front so that encryption is done in correct order. """ for i, public_key in enumerate(reversed(public_keys)): shared_element = private_key * public_key key_material = key_materials[-(i + 1)] # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Encrypt iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) def enc_hmac((index, hmac)): iv = pack("H14s", index, b"\x00" * 14) return aes_ctr_enc_dec(hmac_key, iv, hmac) hmacs = map(enc_hmac, enumerate(hmacs)) ## Generate HMAC h = Hmac(b"sha512", hmac_key) for other_hmac in hmacs: h.update(other_hmac) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs.insert(0, expected_mac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) address_cipher = address_plaintext message_cipher = message_plaintext ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE ###Initialise blinding factor and blindking keys blinding_factor = Bn(1) blinding_keys=[] ##initialise Hmacs array hmacs = [] ###start from second key as first key is not blinded in the mix for i in range(len(public_keys)): ###first key is not blinded so since blinding public_key = blinding_factor*public_keys[i] blinding_keys.append(public_key) ## shared_element = private_key * blinding_keys[i] key_material = sha512(shared_element.export()).digest() blinding_factor *= Bn.from_binary(key_material[48:]) ### reverse order as you encrypt from last mix first for k in reversed(blinding_keys): ##shared element shared_element = private_key * k key_material = sha512(shared_element.export()).digest() ###produce keys for hmac address and message hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ##assign iv for ciphers and encyrpt iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key,iv,address_cipher) message_cipher = aes_ctr_enc_dec(message_key,iv,message_cipher) h = Hmac(b"sha512",hmac_key)## initialise hmac ####produce hmacs temp = [] for j,other in enumerate(hmacs): ###seperate iv for each hmac iv = pack("H14s",j,b"\x00"*14) hmac_plaintext = aes_ctr_enc_dec(hmac_key,iv,other) temp+=[hmac_plaintext] h.update(hmac_plaintext) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] temp =[expected_mac]+temp hmacs=temp return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_server_n_hop(private_key, message_list, use_blinding_factor=False, final=False): """ Decodes a NHopMixMessage message and outputs either messages destined to the next mix or a list of tuples (address, message) (if final=True) to be sent to their final recipients. Broadly speaking the mix will process each message in turn: - it derives a shared key (using its private_key), - checks the first hmac, - decrypts all other parts, - Either forwards or decodes the message. The implementation of the blinding factor is optional and therefore only activated in the bonus tests. """ G = EcGroup() out_queue = [] # Process all messages for msg in message_list: ## Check elements and lengths if not G.check_point(msg.ec_public_key) or \ not isinstance(msg.hmacs, list) or \ not len(msg.hmacs[0]) == 20 or \ not len(msg.address) == 258 or \ not len(msg.message) == 1002: raise Exception("Malformed input message") ## First get a shared key shared_element = private_key * msg.ec_public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Extract a blinding factor for the public_key # (only if you're brave enough for the bonus task) new_ec_public_key = msg.ec_public_key if (use_blinding_factor): blinding_factor = Bn.from_binary(key_material[48:]) new_ec_public_key = blinding_factor * msg.ec_public_key ## Check the HMAC h = Hmac(b"sha512", hmac_key) for other_mac in msg.hmacs[1:]: h.update(other_mac) h.update(msg.address) h.update(msg.message) expected_mac = h.digest() if not secure_compare(msg.hmacs[0], expected_mac[:20]): raise Exception("HMAC check failure") # Decrypt hmacs new_hmacs = [] for i, other_mac in enumerate(msg.hmacs[1:]): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00"*14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_plaintext] # Decrypt address & message iv = b"\x00"*16 address_plaintext = aes_ctr_enc_dec(address_key, iv, msg.address) message_plaintext = aes_ctr_enc_dec(message_key, iv, msg.message) if final: # Decode the address and message address_len, address_full = unpack("!H256s", address_plaintext) message_len, message_full = unpack("!H1000s", message_plaintext) out_msg = (address_full[:address_len], message_full[:message_len]) out_queue += [out_msg] else: # Pass the new mix message to the next mix out_msg = NHopMixMessage(new_ec_public_key, new_hmacs, address_plaintext, message_plaintext) out_queue += [out_msg] return out_queue
def mix_client_n_hop(public_keys, address, message, use_blinding_factor=False): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). The implementation of the blinding factor is optional and therefore only activated in the bonus tests. It can be ignored for the standard task. If you implement the bonus task make sure to only activate it if use_blinding_factor is True. """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() #TODO ADD CODE HERE hmacs = [] keys_count = len(public_keys) for i in range(keys_count-1,-1,-1): new_macs = [] key = public_keys[i] encryption_key = private_key * key ek = sha512(encryption_key.export()).digest() #get coresponding key parts hmac_key = ek[:16] address_key = ek[16:32] message_key = ek[32:48] #encrypt for this hop iv = b"\x00"*16 if (i == keys_count-1): # last hop before destination address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) else: address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) #calculate mac for this hop h = Hmac(b"sha512", hmac_key) if(len(hmacs)>0): for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00"*14) hmac_enc = aes_ctr_enc_dec(hmac_key, iv, other_mac) h.update(hmac_enc) new_macs.append(hmac_enc) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest() expected_mac = expected_mac[:20] #print "server hmac_key : ", hmac_key #print "client addr_key : ", address_key #print "server mesg_key : ", message_key #print "client adr_cipher: ", address_cipher[:75] #print "client msg_cipher: ", message_cipher[:75] new_macs.insert(0,expected_mac) hmacs = new_macs #print "client client_mac: ", expected_mac return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # initilise lists for hmacs and blinding_keys hmacs = [] blinding_keys = [] # initilise variables address_cipher = address_plaintext message_cipher = message_plaintext # initial blinding_factor set to 1 blinding_factor = Bn(1) # append first public key to list blinding_keys.append(public_keys[0]) length = len(public_keys) for i in range(1, length): # generate shared element shared = private_key * blinding_keys[-1] material = sha512(shared.export()).digest() # generate another bliding factor blinding_factor = blinding_factor * Bn.from_binary(material[48:]) # calculate and append blinding factor to a list public_key = public_keys[i] blinding_keys = blinding_keys + [blinding_factor * public_key] # In reverse order calculate message for each hop using generated blinding factors blinding_keys = reversed(blinding_keys) for key in blinding_keys: # generate shared element shared = private_key * key material = sha512(shared.export()).digest() # split shared key for different operations hmac_key = material[:16] address_key = material[16:32] message_key = material[32:48] # random inistialisation vector iv = b"\x00" * 16 # encoding address and message address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # temporary list of hmacs temp_hmacs = [] h = Hmac(b"sha512", hmac_key) for i in range(0, len(hmacs)): prev_mac = hmacs[i] # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) # encode hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, prev_mac) # add the encoded hmac to the temporary list temp_hmacs = temp_hmacs + [hmac_plaintext] h.update(hmac_plaintext) h.update(address_cipher) h.update(message_cipher) # turn h into binary expected_mac = h.digest() # take first 20 bits expected_mac = expected_mac[:20] # insert expected_mac (final mac) to the beggining of the list temp_hmacs = [expected_mac] + temp_hmacs # update hmacs list with temp_hmacs list hmacs = temp_hmacs return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE # Initialize list of key_material key_material_list = [] # Iterate the list of public keys for public_key in public_keys: # Get shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Insert at the head of the list key_material_list.insert(0, key_material) # Extract a blinding factor for the public_key blinding_factor = Bn.from_binary(key_material[48:]) # Change key using blinding factor private_key *= blinding_factor # Initialize hmacs = [] address_cipher = address_plaintext message_cipher = message_plaintext # Iterate the public keys in reverse order for i, public_key in enumerate(public_keys[::-1]): # Use different parts of the shared key for different operations hmac_key = key_material_list[i][:16] address_key = key_material_list[i][16:32] message_key = key_material_list[i][32:48] # Encrypt address & message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Generate HMAC h = Hmac(b"sha512", hmac_key) # Generate new hmacs for i, hmac in enumerate(hmacs): iv = pack("H14s", i, b"\x00" * 14) new_hmac = aes_ctr_enc_dec(hmac_key, iv, hmac) hmacs[i] = new_hmac h.update(hmacs[i]) h.update(address_cipher) h.update(message_cipher) hmac = h.digest()[:20] hmacs.insert(0, hmac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() shared_keys = [] # PART 1 # use of a blinding factor to provide bit-wise unlikability of # the public key associated with the message for public_key in public_keys: # get a shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # store into list from back to front shared_keys = [key_material] + shared_keys # update private key value of client with a blinding factor for next shared key derivation private_key = private_key * Bn.from_binary(key_material[48:]) address_cipher = address_plaintext message_cipher = message_plaintext # PART 2 # inclusion of a (list) of hmacs as the second part of the mix message # done in reverse order to compute hmacs hmacs = [] for i, public_key in enumerate(reversed(shared_keys)): # Use different parts of the shared key for different operations hmac_key = shared_keys[i][:16] address_key = shared_keys[i][16:32] message_key = shared_keys[i][32:48] # Ecrypt the address and the message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Check HMAC h = Hmac(b"sha512", hmac_key) # PART 3 # Encryption of the hmacs (in addition to the address and message) at each step of mixing # loop skipped on first call for j, other_hmac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", j, b"\x00" * 14) hmacs[j] = aes_ctr_enc_dec(hmac_key, iv, other_hmac) h.update(hmacs[j]) h.update(address_cipher) h.update(message_cipher) hmacs = [h.digest()[:20]] + hmacs ## add result to hmacs list return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE # Generate key materials key_materials = [] next_blinding_factor = None for pubkey in public_keys: shared_element = None if not next_blinding_factor is None: private_key *= next_blinding_factor shared_element = private_key * pubkey key_material = sha512(shared_element.export()).digest() hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] next_blinding_factor = Bn.from_binary(key_material[48:]) key_materials += [(hmac_key, address_key, message_key)] new_hmacs = [] address_cipher = bytes(address_plaintext) message_cipher = bytes(message_plaintext) for i, key_material in reversed(list(enumerate(key_materials))): # Encrypt the address and the message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(key_material[1], iv, address_cipher) message_cipher = aes_ctr_enc_dec(key_material[2], iv, message_cipher) # Encrypt the HMACs for i, hmac in enumerate(new_hmacs): iv = pack("H14s", i, b"\x00" * 14) new_hmacs[i] = aes_ctr_enc_dec(key_material[0], iv, hmac) # Compute the HMAC h = Hmac(b"sha512", key_material[0]) for hmac in new_hmacs: h.update(hmac) h.update(address_cipher) h.update(message_cipher) new_hmac = h.digest()[:20] new_hmacs = [new_hmac] + new_hmacs return NHopMixMessage(client_public_key, new_hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) # Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() shared_keys = [] hmacs = [] # no blinding factor between Alice and the first mix new_private_key = private_key # the first ciphertext is the plaintext address_cipher = address_plaintext message_cipher = message_plaintext # generate new private key for each mix's public key for public_key in public_keys: # get a shared key shared_element = new_private_key * public_key key_material = sha512(shared_element.export()).digest() # prepend the shared key to the list of shared keys shared_keys = [key_material] + shared_keys # update the private key's value with a blinding factor new_private_key = new_private_key * Bn.from_binary(key_material[48:]) # iterate through the public keys in the reverse order, because we want # to compute hmacs in the reverse order for i, public_key in enumerate(public_keys[::-1]): # Use different parts of the shared key for different operations hmac_key = shared_keys[i][:16] address_key = shared_keys[i][16:32] message_key = shared_keys[i][32:48] # Decrypt the address and the message iv = b"\x00" * 16 # we encrypt the ciphertext address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Create the HMAC h = Hmac(b"sha512", hmac_key) # encrypt the known hmacs for j, hmac in enumerate(hmacs): # Ensure the IV is different for each hmac and encode it iv = pack("H14s", j, b"\x00" * 14) hmacs[j] = aes_ctr_enc_dec(hmac_key, iv, hmac) h.update(hmacs[j]) # finish building the hmac h.update(address_cipher) h.update(message_cipher) # prepend the result to the list of hmacs hmacs = [h.digest()[:20]] + hmacs return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() address_cipher = None message_cipher = None hmacs = [] blinded_public_keys = [public_keys[0]] blinding_factor = Bn(1) for public_key in public_keys[1:]: ## First get a shared key shared_element = private_key * blinded_public_keys[-1] key_material = sha512(shared_element.export()).digest() blinding_factor *= Bn.from_binary(key_material[48:]) blind_pk = blinding_factor * public_key blinded_public_keys.append(blind_pk) blinded_public_keys.reverse() for public_key in blinded_public_keys: # ## First get a shared key # Get a shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] h = Hmac(b"sha512", hmac_key) for other_mac in hmacs: h.update(other_mac) if address_cipher is None: address_cipher = aes_ctr_enc_dec(address_key, b'\x00' * 16, address_plaintext) else: address_cipher = aes_ctr_enc_dec(address_key, b'\x00' * 16, address_cipher) if message_cipher is None: message_cipher = aes_ctr_enc_dec(message_key, b'\x00' * 16, message_plaintext) else: message_cipher = aes_ctr_enc_dec(message_key, b'\x00' * 16, message_cipher) h.update(address_cipher) h.update(message_cipher) mac = h.digest()[:20] hmacs.append(mac) hmacs.reverse() return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # This assertion was originally commented out, can instead check each of the keys one by one for public_key in public_keys: assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE # Calculate shared keys, taking into account blinding shared_elems = [] blinded = [] new = public_keys[0] for i, public_key in enumerate(public_keys): shared = private_key * new shared_elems.append(shared) if i == len(public_keys) - 1: break key_material = sha512(shared.export()).digest() blinding = Bn.from_binary(key_material[48:]) blinded.append(blinding) blind = blinded[0] for b in range(len(blinded[1:])): blind = blind * blinded[b + 1] new = blind * public_keys[i + 1] # Shared keys are used 'backwards' shared_elems.reverse() public_keys.reverse() # At the start of the encryption chain, the payload is in plaintext address_cipher = address_plaintext message_cipher = message_plaintext # Perform the encoding at each stage, using the shared elements calculated hmacs = [] for i, public_key in enumerate(public_keys): shared_element = shared_elems[i] # Get the appropriate key from each part of the shared key key_material = sha512(shared_element.export()).digest() hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Encrypt the message and address at each stage iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Encrypt the hmacs at each stage for j, hm in enumerate(hmacs): iv = pack("H14s", j, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(key_material[:16], iv, hm) hmacs[j] = hmac_plaintext # Generate the expected mac for each stage h = Hmac(b"sha512", hmac_key) for hm in hmacs: h.update(hm) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] # Each hmac is inserted at the start of the list, as the last hmac is the first to be # inspected - the last encryption is also the first to be decrypted hmacs.insert(0, expected_mac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() hmacs = [] new_public_keys = [] # process the keys with blinding_factor blinding_factor = Bn(1) for public_key in public_keys: public_key = blinding_factor * public_key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Extract a blinding factor for the public_key blinding_factor *= Bn.from_binary(key_material[48:]) new_public_keys.insert( 0, public_key) # we need to encode messages in reverse order address_cipher = address_plaintext message_cipher = message_plaintext for public_key in new_public_keys: shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Decrypt address & message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Check the HMAC h = Hmac(b"sha512", hmac_key) # Decrypt hmacs new_hmacs = [] for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_plaintext] h.update(hmac_plaintext) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs = new_hmacs hmacs.insert(0, expected_mac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE hmacs = [] shared_keys = [] ## generate all used shared key for public_key in public_keys: shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() ## Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] ## Extract a blinding factor for next private_key blinding_factor = Bn.from_binary(key_material[48:]) private_key = blinding_factor * private_key shared_keys.append([hmac_key, address_key, message_key]) for shared_key in reversed(shared_keys): ## get the shared key hmac_key, address_key, message_key = shared_key[0], shared_key[ 1], shared_key[2] ## Encrypt the address and the message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) address_plaintext = address_cipher message_plaintext = message_cipher ## HMAC new_hmacs = [] h = Hmac(b"sha512", hmac_key) # Encrypt hmacs for i, hmac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_ciphertext = aes_ctr_enc_dec(hmac_key, iv, hmac) h.update(hmac_ciphertext) new_hmacs += [hmac_ciphertext] h.update(address_cipher) h.update(message_cipher) hmac = h.digest()[:20] new_hmacs.insert(0, hmac) hmacs = new_hmacs return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) hmacs = [] address_cipher = address_plaintext message_cipher = message_plaintext ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() blinding_factor = Bn(1) new_ec_public_keys = [] ## ADD CODE HERE i = 0 for public_key in public_keys: # We don't want to blind the first key if i == 0: new_ec_public_keys.append(public_key) else: ## First get a shared key ## public key part is the last seen public key shared_element = private_key * new_ec_public_keys[-1] key_material = sha512(shared_element.export()).digest() # Building up list of all public keys - they have been blinded blinding_factor *= Bn.from_binary(key_material[48:]) new_ec_public_keys.append(blinding_factor * public_key) i += 1 # Want to encrypt message with the first mix's key on the outside - reverse our public keys for this for key in reversed(new_ec_public_keys): ## First get a shared key shared_element = private_key * key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] iv = b"\x00" * 16 # Build cipher on top of cipher from last mix to create message such as P1(P2(P3...(M)...)) address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) ## Check the HMAC h = Hmac(b"sha512", hmac_key) # Decrypt hmacs new_hmacs = [] for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) h.update(hmac_plaintext) new_hmacs += [hmac_plaintext] h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs = [expected_mac] + new_hmacs return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() # Get list of private keys multiplied with bliding factors private_keys = list() for public_key in public_keys: # Add private key to list private_keys.append(private_key) # Get shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Extract a blinding factor for the private_key blinding_factor = Bn.from_binary(key_material[48:]) private_key = blinding_factor * private_key hmacs = list() address_cipher = address_plaintext message_cipher = message_plaintext for private_key, public_key in reversed(zip(private_keys, public_keys)): # Get shared key shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Encrypt address & message iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) # Generate next HMAC and encrypt previous HMACs h = Hmac(b"sha512", hmac_key) for i in range(0, len(hmacs)): iv = pack("H14s", i, b"\x00"*14) hmacs[i] = aes_ctr_enc_dec(hmac_key, iv, hmacs[i]) h.update(hmacs[i]) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs.insert(0, expected_mac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message, use_blinding_factor=False): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). The implementation of the blinding factor is optional and therefore only activated in the bonus tests. It can be ignored for the standard task. If you implement the bonus task make sure to only activate it if use_blinding_factor is True. """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() #TODO ADD CODE HERE hmacs = [] address_cipher = address_plaintext message_cipher = message_plaintext for public_key in reversed(public_keys): new_hmacs = [] shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Encrypt address & message iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmac_cipher = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs.append(hmac_cipher) hmacs = list(reversed(new_hmacs)) h = Hmac(b"sha512", hmac_key) for other_mac in reversed(hmacs): h.update(other_mac) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs.append(expected_mac) hmacs.reverse() return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE shared_element = None address_cipher = None message_cipher = None hmacs = [] hmackeys = [] messkeys = [] addkeys = [] messciphers = [] addciphers = [] for i in range(len(public_keys)): ## First get a shared key #* shared_element = private_key * public_keys[i] key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] # This section: like the beginning of mix_server_n_hop address_key = key_material[16:32] message_key = key_material[32:48] # Extract a blinding factor for the new private key blinding_factor = Bn.from_binary(key_material[48:]) private_key = blinding_factor * private_key #* # Add keys to corresponding arrays hmackeys += [hmac_key] addkeys += [address_key] messkeys += [message_key] for i in range(len(public_keys)): # Encrypt address & message iv = b"\x00"*16 if i==0: address_cipher = aes_ctr_enc_dec(addkeys[len(public_keys)-1-i], iv, address_plaintext) message_cipher = aes_ctr_enc_dec(messkeys[len(public_keys)-1-i], iv, message_plaintext) else: address_cipher = aes_ctr_enc_dec(addkeys[len(public_keys)-1-i], iv, address_cipher) message_cipher = aes_ctr_enc_dec(messkeys[len(public_keys)-1-i], iv, message_cipher) messciphers += [message_cipher] addciphers += [address_cipher] for i in range(len(public_keys)): # Encryption of hmacs. Individual as we need whole ciphertext to compute macs correctly # Use mac keys in reverse as last ciphertext needs first key new_hmacs = [] for j, other_mac in enumerate(hmacs): # Ensure IV is not the same for each hmac iv = pack("H14s", j, b"\x00"*14) hmac_plaintext = aes_ctr_enc_dec(hmackeys[len(public_keys)-1-i], iv, other_mac) new_hmacs += [hmac_plaintext] h = Hmac(b"sha512", hmackeys[len(public_keys)-1-i]) # Calculate new digest new_hmacs = new_hmacs[::-1] for other_mac in reversed(new_hmacs): h.update(other_mac) h.update(addciphers[i]) h.update(messciphers[i]) digest = h.digest() digest = digest[:20] new_hmacs += [digest] hmacs = new_hmacs[::-1] hmacs = new_hmacs[::-1] return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## Get shared keys (top-down) keyring = namedtuple("Keyring", ["hmac_key", "address_key", "message_key"]) shared_keys = [] accumulated_blinding_factor = Bn(1) for public_key in public_keys: shared_key = accumulated_blinding_factor * private_key * public_key key_material = sha512(shared_key.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] blinding_factor = Bn.from_binary(key_material[48:]) accumulated_blinding_factor = accumulated_blinding_factor * blinding_factor # Store keyring shared_keys.append(keyring(hmac_key, address_key, message_key)) ## Construct NHop message (bottom-up) hmacs = [] address_cipher = address_plaintext message_cipher = message_plaintext for shared_key in reversed(shared_keys): # Encrypt address and message; accumulate iv = b"\x00" * 16 address_cipher = aes_ctr_enc_dec(shared_key.address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(shared_key.message_key, iv, message_cipher) # Encrypt other HMACs for i, hmac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00" * 14) hmacs[i] = aes_ctr_enc_dec(shared_key.hmac_key, iv, hmac) # Get HMAC h = Hmac(b"sha512", shared_key.hmac_key) for hmac in hmacs: h.update(hmac) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs.insert(0, expected_mac) return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message, use_blinding_factor=False): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). The implementation of the blinding factor is optional and therefore only activated in the bonus tests. It can be ignored for the standard task. If you implement the bonus task make sure to only activate it if use_blinding_factor is True. """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() #TODO ADD CODE HERE hmacs = [] address_cipher = address_plaintext message_cipher = message_plaintext for public_key in reversed(public_keys): new_hmacs = [] shared_element = private_key * public_key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] # Encrypt address & message iv = b"\x00"*16 address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac iv = pack("H14s", i, b"\x00"*14) hmac_cipher = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs.append(hmac_cipher) hmacs = list(reversed(new_hmacs)) h = Hmac(b"sha512", hmac_key) for other_mac in reversed(hmacs): h.update(other_mac) h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs.append(expected_mac) hmacs.reverse() return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() blinding_factor = Bn(1) # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE hmacs = [] address_cipher = address_plaintext message_cipher = message_plaintext blinded_public_keys = [public_keys[0]] for public_key in public_keys[1:]: ## First get a shared key shared_element = private_key * blinded_public_keys[-1] key_material = sha512(shared_element.export()).digest() blinding_factor *= Bn.from_binary(key_material[48:]) blinded_public_keys.append(blinding_factor * public_key) for key in reversed(blinded_public_keys): ## First get a shared key shared_element = private_key * key key_material = sha512(shared_element.export()).digest() # Use different parts of the shared key for different operations hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] address_cipher = aes_ctr_enc_dec(address_key, b"\x00" * 16, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, b"\x00" * 16, message_cipher) ## Check the HMAC h = Hmac(b"sha512", hmac_key) # implement hmacs new_hmacs = [] for i, other_mac in enumerate(hmacs): # Ensure the IV is different for each hmac hmac_plaintext = aes_ctr_enc_dec(hmac_key, pack("H14s", i, b"\x00" * 14), other_mac) h.update(hmac_plaintext) new_hmacs += [hmac_plaintext] h.update(address_cipher) h.update(message_cipher) expected_mac = h.digest()[:20] hmacs = [expected_mac] + new_hmacs return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)
def mix_client_n_hop(public_keys, address, message): """ Encode a message to travel through a sequence of mixes with a sequence public keys. The maximum size of the final address and the message are 256 bytes and 1000 bytes respectively. Returns an 'NHopMixMessage' with four parts: a public key, a list of hmacs (20 bytes each), an address ciphertext (256 + 2 bytes) and a message ciphertext (1002 bytes). """ G = EcGroup() # assert G.check_point(public_key) assert isinstance(address, bytes) and len(address) <= 256 assert isinstance(message, bytes) and len(message) <= 1000 # Encode the address and message # use those encoded values as the payload you encrypt! address_plaintext = pack("!H256s", len(address), address) message_plaintext = pack("!H1000s", len(message), message) ## Generate a fresh public key private_key = G.order().random() client_public_key = private_key * G.generator() ## ADD CODE HERE address_cipher = None message_cipher = None hmacs = [] key_materials = [] # We loop once to generate the blinded keys and corresponding key_materials for i in range(len(public_keys)): # Generate the encryption key shared_element = private_key * public_keys[i] key_material = sha512(shared_element.export()).digest() # Add the key generated with the blindness added since private key gets overriden key_materials += [key_material] # Extract a blinding factor for the public_key blinding_factor = Bn.from_binary(key_material[48:]) private_key = blinding_factor * private_key # Reverse the calculated key_materials for correct ordering as per the server key_materials = key_materials[::-1] for i in range(len(public_keys)): key_material = key_materials[i] hmac_key = key_material[:16] address_key = key_material[16:32] message_key = key_material[32:48] iv = b"\x00" * 16 # First we want to encrypt the plaintext, then it will be the ciphertext if i == 0: address_cipher = aes_ctr_enc_dec(address_key, iv, address_plaintext) message_cipher = aes_ctr_enc_dec(message_key, iv, message_plaintext) else: address_cipher = aes_ctr_enc_dec(address_key, iv, address_cipher) message_cipher = aes_ctr_enc_dec(message_key, iv, message_cipher) new_hmacs = [] for j, other_mac in enumerate(reversed(hmacs)): # Ensure the IV is different for each hmac iv = pack("H14s", j, b"\x00" * 14) hmac_plaintext = aes_ctr_enc_dec(hmac_key, iv, other_mac) new_hmacs += [hmac_plaintext] h = Hmac(b"sha512", hmac_key) new_hmacs = new_hmacs[::-1] # Add all other macs plus the address and message cipher to the hmac for other_mac in reversed(new_hmacs): h.update(other_mac) h.update(address_cipher) h.update(message_cipher) # take only the first 20 bytes digest = h.digest()[:20] # Add the calculated hmac new_hmacs += [digest] hmacs = new_hmacs hmacs = new_hmacs[::-1] return NHopMixMessage(client_public_key, hmacs, address_cipher, message_cipher)