def test_cbc_oram_enc_dec(): aes = Cipher("AES-128-CBC") key = urandom(16) iv = urandom(16) ipt = [urandom(16), "Hello"] IV, data = ipt IV0 = KDF(iv, IV).iv enc = aes.enc(key, IV0) data = enc.update(data) data += enc.finalize() IV1 = KDF(iv, data[0:16]).iv enc = aes.enc(key, IV1) IV = enc.update(IV) IV += enc.finalize() datablock = [IV, data] IV, data = datablock IV1 = KDF(iv, data[0:16]).iv dec = aes.dec(key, IV1) IV = dec.update(IV) IV += dec.finalize() IV0 = KDF(iv, IV).iv dec = aes.dec(key, IV0) data = dec.update(data) data += dec.finalize() datablock = [IV, data] assert ipt == datablock
def test_cbc_enc_dec(): aes = Cipher("AES-128-CBC") key = urandom(16) iv = urandom(16) ipt = "Hello" enc = aes.enc(key, iv) ciphertext = enc.update(ipt) ciphertext += enc.finalize() dec = aes.dec(key, iv) plaintext = dec.update(ciphertext) plaintext += dec.finalize() assert ipt == plaintext
def mix_operate(message, triplet, setup): mname, mpub, msec = triplet elem, forward, backwards = message G, o, g, o_bytes = setup aes = Cipher("AES-128-CTR") # Derive first key k1 = KDF((msec * elem).export()) # Derive the blinding factor b = Bn.from_binary(k1.b) % o new_elem = b * elem # Check the forward MAC mac1 = hmac.new(k1.kmac, forward[20:], digestmod=sha1).digest() assert forward[:20] == mac1 # Decrypt the payload enc = aes.dec(k1.kenc, k1.iv) pt = enc.update(forward[20:]) pt += enc.finalize() # Parse the forward message xfrom, xto, the_bs, new_forw = pt[:4], pt[4:8], pt[8:8+o_bytes], pt[8+o_bytes:] old_bs = Bn.from_binary(the_bs) # Now encrypt the return part k2 = KDF(((msec * old_bs) * elem).export()) new_bs = old_bs.mod_inverse(o).binary() enc = aes.enc(k2.kenc, k2.iv) new_back_body = enc.update(xto + xfrom + new_bs + backwards) new_back_body += enc.finalize() mac2 = hmac.new(k2.kmac, new_back_body, digestmod=sha1).digest() new_back = mac2 + new_back_body return ((xfrom, xto), (new_elem, new_forw, new_back) )
class SphinxParams: def __init__(self, group=None, header_len=192, body_len=1024, assoc_len=0, k=16, dest_len=16): self.aes = Cipher("AES-128-CTR") self.cbc = Cipher("AES-128-CBC") self.assoc_len = assoc_len self.max_len = header_len self.zero_pad = b"\x00" * (2 * self.max_len) self.m = body_len self.k = k self.dest_len = dest_len self.group = group if not group: self.group = Group_ECC() # The LIONESS PRP def aes_ctr(self, k, m, iv=zero_iv): #k = bytes(k) #m = bytes(m) c = self.aes.enc(k, iv).update(m) return bytes(c) # The LIONESS PRP def aes_cbc_enc(self, k, m, iv=zero_iv): cipher = self.cbc.enc(k, iv) cipher.set_padding(False) c = cipher.update(m) #c = c + cipher.finalize() return bytes(c) # The LIONESS PRP def aes_cbc_dec(self, k, m, iv=zero_iv): cipher = self.cbc.dec(k, iv) cipher.set_padding(False) c = cipher.update(m) #c = c + cipher.finalize() return bytes(c) def lioness_enc(self, key, message): assert len(key) == self.k assert len(message) >= self.k * 2 # Round 1 k1 = self.hash(message[self.k:] + key + b'1')[:self.k] c = self.aes_ctr(key, message[:self.k], iv=k1) r1 = c + message[self.k:] # Round 2 c = self.aes_ctr(key, r1[self.k:], iv=r1[:self.k]) r2 = r1[:self.k] + c # Round 3 k3 = self.hash(r2[self.k:] + key + b'3')[:self.k] c = self.aes_ctr(key, r2[:self.k], iv=k3) r3 = c + r2[self.k:] # Round 4 c = self.aes_ctr(key, r3[self.k:], r3[:self.k]) r4 = r3[:self.k] + c return r4 def lioness_dec(self, key, message): assert len(key) == self.k assert len(message) >= self.k * 2 r4 = message r4_short, r4_long = r4[:self.k], r4[self.k:] # Round 4 r3_long = self.aes_ctr(key, r4_long, iv=r4_short) r3_short = r4_short # Round 3 k2 = self.hash(r3_long + key + b'3')[:self.k] r2_short = self.aes_ctr(key, r3_short, iv=k2) r2_long = r3_long # Round 2 r1_long = self.aes_ctr(key, r2_long, iv=r2_short) r1_short = r2_short # Round 1 k0 = self.hash(r1_long + key + b'1')[:self.k] c = self.aes_ctr(key, r1_short, iv=k0) r0 = c + r1_long return r0 # AES-CTR operation def xor_rho(self, key, plain): assert len(key) == self.k return self.aes_ctr(key, plain) # The HMAC; key is of length k, output is of length k def mu(self, key, data): mac = hmac.new(key, data, digestmod=sha256).digest()[:self.k] return mac # The PRP; key is of length k, data is of length m def pi(self, key, data): assert len(key) == self.k assert len(data) == self.m return self.lioness_enc(key, data) # The inverse PRP; key is of length k, data is of length m def pii(self, key, data): assert len(key) == self.k assert len(data) == self.m return self.lioness_dec(key, data) def small_perm(self, key, data): assert len(data) == self.k # aes = Cipher("AES-128-CBC") enc = self.cbc.enc(key, None) enc.set_padding(False) c = enc.update(data) #c += enc.finalize() return c def small_perm_inv(self, key, data): assert len(data) == self.k # aes = Cipher("AES-128-CBC") dec = self.cbc.dec(key, None) dec.set_padding(False) c = dec.update(data) #c += dec.finalize() return c # The various hashes def hash(self, data): return sha256(data).digest() def get_aes_key(self, s): group = self.group return bytes(self.hash(b"aes_key:" + group.printable(s))[:self.k]) def get_aes_key_all(self, s): group = self.group k = self.hash(b"aes_key:" + group.printable(s))[:self.k] num = 3 # (hrho, hmu, htau) iv = b"UltrUltrUltrUltr" material = self.aes.enc(k, iv).update(b"\x00" * self.k * num) return k, [material[self.k * i:self.k * (i + 1)] for i in range(num)] def derive_key(self, k, flavor): assert len(k) == len(flavor) == self.k iv = flavor m = b"\x00" * self.k K = self.aes.enc(k, iv).update(m) return K def hb(self, k): "Compute a hash of alpha and s to use as a blinding factor" K = self.derive_key(k, b"hbhbhbhbhbhbhbhb") return self.group.makeexp(K) def hrho(self, k): "Compute a hash of s to use as a key for the PRG rho" K = self.derive_key(k, b"hrhohrhohrhohrho") return K def hmu(self, k): "Compute a hash of s to use as a key for the HMAC mu" K = self.derive_key(k, b"hmu:hmu:hmu:hmu:") return K # def hmu2(self, k): # "Compute a hash of s to use as a key for the permutation mu2" # K = self.derive_key(k, b"hmu2:hmu2:hmu2:h") # return K def hpi(self, k): "Compute a hash of s to use as a key for the PRP pi" K = self.derive_key(k, b"hpi:hpi:hpi:hpi:") return K def htau(self, k): "Compute a hash of s to use to see if we've seen s before" K = self.derive_key(k, b"htauhtauhtauhtau") return K def h_body_K(self, k): "The Ultrix key to protect the user data." K = self.derive_key(k, b"UbodUbodUbodUbod") return K def h_root_K(self, k): "The Ultrix key to protect the root key." K = self.derive_key(k, b"UrooUrooUrooUroo") return K def derive_user_keys(self, k, iv, number=2): material = self.aes.enc(k, iv).update(b"\x00" * self.k * number) st_ranges = range(0, self.k * number, self.k) return [material[st:st + self.k] for st in st_ranges]
def mix_package(sender, receiver, triplets): ''' Package a message through a mix-net. ''' aes = Cipher("AES-128-CTR") Bs = [] _, ypub, y = sender pubs = [ ypub ] round_trip = triplets + [ receiver ] + list(reversed(triplets)) secrets = [] prod_bs = Bn(1) for i, (mname, mpub, msec) in enumerate(round_trip): xysec2 = (y * prod_bs) * mpub secrets += [ xysec2 ] if __debug__ and msec is not None: xysec1 = (msec * prod_bs) * ypub assert xysec2 == xysec1 # Blinding factor k = KDF(xysec2.export()) b = Bn.from_binary(k.b) % o Bs += [ b ] # y = (b * y) % o prod_bs = (b * prod_bs) % o #ypub = b * ypub pubs += [ prod_bs * ypub ] # Precompute the correction factors correction_factors = [] for i in range(len(triplets)): total_b = Bn(1) for j in range(i, 2 * len(triplets) - i): total_b = (total_b * Bs[j]) % o assert round_trip[i][0] == round_trip[2 * len(triplets) - i][0] assert total_b * pubs[i] == pubs[2 * len(triplets) - i] correction_factors += [ total_b ] all_factors = [] + correction_factors all_factors += [ Bn(1) ] all_factors += [bf.mod_inverse(o) for bf in reversed(correction_factors)] assert len(all_factors) == len(round_trip) # Generate data stream data = [ sender ] + round_trip + [ sender ] addressing = [] for i, _ in enumerate(round_trip): addressing += [(data[1 + i-1][0], data[1 + i+1][0])] # Derive all keys all_data = zip(round_trip, all_factors, pubs, addressing, secrets) all_keys = [] for (mname, mpub, msec), bs, yelem, (xfrom, xto), Ksec in all_data: k1 = KDF(Ksec.export()) k2 = KDF( (bs * Ksec).export()) all_keys += [(k1, k2)] all_data = zip(round_trip, all_factors, pubs, addressing, all_keys) # Build the backwards path prev = '' backwards_stages = [ ] for j in range(len(mix_names) + 1): (mname, mpub, msec), bs, yelem, (xfrom, xto), (k1, k2) = all_data[j] the_bs = bs.mod_inverse(o).binary() enc = aes.enc(k2.kenc, k2.iv) ciphertext = enc.update(xto + xfrom + the_bs + prev) ciphertext += enc.finalize() mac = hmac.new(k2.kmac, ciphertext, digestmod=sha1).digest() prev = mac + ciphertext backwards_stages += [ prev ] # Build the forwards path prev = '' forwards_stages = [] for jp in range(len(mix_names) + 1): j = len(mix_names) - jp (mname, mpub, msec), bs, yelem, (xfrom, xto), (k1, k2) = all_data[j] the_bs = bs.binary() enc = aes.enc(k1.kenc, k1.iv) ciphertext = enc.update(xfrom + xto + the_bs + prev) ciphertext += enc.finalize() mac = hmac.new(k1.kmac, ciphertext, digestmod=sha1).digest() prev = mac + ciphertext forwards_stages += [ prev ] forwards_stages = list(reversed(forwards_stages)) stages = zip(forwards_stages, backwards_stages) # Check all the MACs if __debug__: for j in range(len(mix_names) + 1): (msg_f, msg_b) = stages.pop(0) (mname, mpub, msec), bs, yelem, (xfrom, xto), (k1, k2) = all_data[j] mac1 = hmac.new(k1.kmac, msg_f[20:], digestmod=sha1).digest() assert msg_f[:20] == mac1 enc = aes.dec(k1.kenc, k1.iv) plaintext = enc.update(msg_f[20:]) plaintext += enc.finalize() assert xfrom == plaintext[:4] and xto == plaintext[4:8] mac2 = hmac.new(k2.kmac, msg_b[20:], digestmod=sha1).digest() assert msg_b[:20] == mac2 # End __debug__ return zip(pubs[:len(mix_names) + 1], forwards_stages + [''], [''] + backwards_stages)
def mix_operate(message, triplet, setup, generate_return_message=False): ''' Operate a Mix with a received message, and its keys. ''' mname, mpub, msec = triplet elem, forward, backwards = message G, o, g, o_bytes = setup aes = Cipher("AES-128-CTR") # Derive first key k1 = KDF((msec * elem).export()) # Derive the blinding factor b = Bn.from_binary(k1.b) % o new_elem = b * elem # Check the forward MAC mac1 = hmac.new(k1.kmac, forward[20:], digestmod=sha1).digest() if not (forward[:20] == mac1): raise Exception("Wrong MAC1") # Decrypt the payload enc = aes.dec(k1.kenc, k1.iv) pt = enc.update(forward[20:]) pt += enc.finalize() # Parse the forward message xcode = pt[0] if not (xcode == "0" or xcode == "1"): raise Exception("Wrong routing code") pt = pt[1:] if xcode == "0": xfrom, xto, the_bs, new_forw = pt[:4], pt[4:8], pt[8:8+o_bytes], pt[8+o_bytes:] old_bs = Bn.from_binary(the_bs) # Now package the return part k2 = KDF(((msec * old_bs) * elem).export()) enc = aes.enc(k2.kenc, k2.iv) new_back_body = enc.update("1" + xto + xfrom + backwards) new_back_body += enc.finalize() mac2 = hmac.new(k2.kmac, new_back_body, digestmod=sha1).digest() new_back = mac2 + new_back_body if generate_return_message: ret_elem = old_bs * elem ret_forw = new_back ret_back = None return ((xto, xfrom), (ret_elem, ret_forw, ret_back) ) else: xfrom, xto, new_forw = pt[:4], pt[4:8], pt[8:] # Returns do not need to build returns if not (backwards == None): raise Exception("Backwards header should be None") new_back = None return ((xfrom, xto), (new_elem, new_forw, new_back) )