Example #1
0
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
Example #2
0
 def aes_enc_dec(self, key, iv, input_):
     """A helper function which implements the AES-128 encryption in counter mode CTR"""
     aes = Cipher("AES-128-CTR")
     enc = aes.enc(key, iv)
     output = enc.update(input_)
     output += enc.finalize()
     return output
Example #3
0
 def aes_enc_dec(self, data):
  ''' AES Enc/Dec '''
  aes = Cipher("AES-128-CBC")
  enc = aes.enc(self.key, self.ctr_iv)
  output = enc.update(data)
  output += enc.finalize()
  return output
Example #4
0
def test_ctr_enc_dec():

    aes = Cipher("AES-128-CTR")
    key = urandom(16)
    iv = urandom(16)
    enc = aes.enc(key, iv)
    ipt = "Hello"

    ciphertext = enc.update(ipt)
    ciphertext += enc.finalize()

    dec = aes.enc(key, iv)
    plaintext = dec.update(ciphertext)
    plaintext += dec.finalize()

    assert ipt == plaintext
Example #5
0
 def aes_cbc(self, key, IV, data):
     #Input: Encryption key, Initialization vector, data to encrypt
     #Output: Encrypted data with IV and key
     aes = Cipher("AES-128-CBC")
     enc = aes.enc(key, IV)
     output = enc.update(data)
     output += enc.finalize()
     return output
Example #6
0
def aes_ctr_enc_dec(key, iv, input):
    """ A helper function that implements AES Counter (CTR) Mode encryption and decryption. 
    Expects a key (16 byte), and IV (16 bytes) and an input plaintext / ciphertext.
    If it is not obvious convince yourself that CTR encryption and decryption are in 
    fact the same operations.
    """

    aes = Cipher("AES-128-CTR")

    enc = aes.enc(key, iv)
    output = enc.update(input)
    output += enc.finalize()

    return output
def aes_ctr_enc_dec(key, iv, input):
    """ A helper function that implements AES Counter (CTR) Mode encryption and decryption. 
    Expects a key (16 byte), and IV (16 bytes) and an input plaintext / ciphertext.

    If it is not obvious convince yourself that CTR encryption and decryption are in 
    fact the same operations.
    """
    
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key, iv)
    output = enc.update(input)
    output += enc.finalize()

    return output
Example #8
0
    def cascade_rebuild(self, seed, key, iv, flags, inverse, data):
        #Cascade Rebuild data processing function
        #print "MIX: Cascade Rebuild enc/perm"
        if not inverse and not flags[2]:
            data = self.permute(seed, data, inverse)

        aes = Cipher("AES-128-CTR")
        enc = aes.enc(key, iv)

        #for i in range(len(data)):
        #	data[i] = enc.update(data[i])
        #	data[i] += enc.finalize()

        if inverse and not flags[2]:
            data = self.permute(seed, data, inverse)
        return data
Example #9
0
    def parallel_rebuild(self, seed, key, iv, flags, inverse, data):
        #Parallel Rebuild data processing function
        if not inverse and not flags[2]:
            #print "Par Rebuild enc/perm : permute", seed, data, inverse
            data = self.permute(seed, data, inverse)

        aes = Cipher("AES-128-CTR")
        enc = aes.enc(key, iv)

        #for i in range(len(data)):
        #	data[i] = enc.update(data[i])
        #	data[i] += enc.finalize()

        if inverse and not flags[2]:
            #print "Par Rebuild enc/perm : permute", seed, data, inverse
            data = self.permute(seed, data, inverse)
        return data
Example #10
0
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) )
Example #11
0
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]
Example #12
0
def test_sortpermuteandencrypt():
    m1 = Mix("M1", "localhost", 8001, 0, 0, 0)
    m1.mix.records = 9
    m1.mix.list = []
    for i in range(3):
        m1.mix.list.extend([i])
    m1.mix.index = 0

    m2 = Mix("M2", "localhost", 8002, 0, 0, 0)
    m2.mix.records = 9
    m2.mix.list = []
    for i in range(3):
        m2.mix.list.extend([i])
    m2.mix.index = 1

    m3 = Mix("M3", "localhost", 8003, 0, 0, 0)
    m3.mix.records = 9
    m3.mix.list = []
    for i in range(3):
        m3.mix.list.extend([i])
    m3.mix.index = 2

    pubseed = urandom(16)
    pubseed2 = urandom(16)
    seed1 = urandom(16)
    seed2 = urandom(16)
    seed3 = urandom(16)
    key1 = urandom(16)
    key2 = urandom(16)
    key3 = urandom(16)
    iv1 = urandom(16)
    iv2 = urandom(16)
    iv3 = urandom(16)

    nbmix = 3
    data1 = []
    for i in range(3):
        data1.extend([str(i) * 4])
    data2 = []
    for i in range(3):
        data2.extend([str(i + 3) * 4])
    data3 = []
    for i in range(3):
        data3.extend([str(i + 2 * 3) * 4])

    alloc1 = m1.mix.permute_global(pubseed, 9, nbmix, 1)
    sortout1 = m1.mix.sort_global_out(data1, alloc1)

    alloc2 = m2.mix.permute_global(pubseed, 9, nbmix, 1)
    sortout2 = m2.mix.sort_global_out(data2, alloc2)

    alloc3 = m3.mix.permute_global(pubseed, 9, nbmix, 1)
    sortout3 = m3.mix.sort_global_out(data3, alloc3)

    print "----------------------------------------------------------"

    received1 = [sortout1[0], sortout2[0], sortout3[0]]
    received2 = [sortout1[1], sortout2[1], sortout3[1]]
    received3 = [sortout1[2], sortout2[2], sortout3[2]]

    alloc1 = m1.mix.permute_global(pubseed, 9, nbmix, 1)
    sortin1 = m1.mix.sort_global_in(received1, alloc1)
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key1, iv1)
    for i in range(len(sortin1)):
        sortin1[i] = enc.update(sortin1[i])
        sortin1[i] += enc.finalize()
    sortin1 = m1.mix.permute(seed1, sortin1, 1)

    alloc2 = m2.mix.permute_global(pubseed, 9, nbmix, 1)
    sortin2 = m2.mix.sort_global_in(received2, alloc2)
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key2, iv2)
    for i in range(len(sortin2)):
        sortin2[i] = enc.update(sortin2[i])
        sortin2[i] += enc.finalize()
    sortin2 = m2.mix.permute(seed2, sortin2, 1)

    alloc3 = m3.mix.permute_global(pubseed, 9, nbmix, 1)
    sortin3 = m3.mix.sort_global_in(received3, alloc3)
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key3, iv3)
    for i in range(len(sortin3)):
        sortin3[i] = enc.update(sortin3[i])
        sortin3[i] += enc.finalize()
    sortin3 = m3.mix.permute(seed3, sortin3, 1)

    alloc1 = m1.mix.permute_global(pubseed2, 9, nbmix, 1)
    sortout1 = m1.mix.sort_global_out(sortin1, alloc1)

    alloc2 = m2.mix.permute_global(pubseed2, 9, nbmix, 1)
    sortout2 = m2.mix.sort_global_out(sortin2, alloc2)

    alloc3 = m3.mix.permute_global(pubseed2, 9, nbmix, 1)
    sortout3 = m3.mix.sort_global_out(sortin3, alloc3)

    print "----------------------------------------------------------"

    received1 = [sortout1[0], sortout2[0], sortout3[0]]
    received2 = [sortout1[1], sortout2[1], sortout3[1]]
    received3 = [sortout1[2], sortout2[2], sortout3[2]]

    alloc1 = m1.mix.permute_global(pubseed2, 9, nbmix, 1)
    sortin1 = m1.mix.sort_global_in(received1, alloc1)

    alloc2 = m2.mix.permute_global(pubseed2, 9, nbmix, 1)
    sortin2 = m2.mix.sort_global_in(received2, alloc2)

    alloc3 = m3.mix.permute_global(pubseed2, 9, nbmix, 1)
    sortin3 = m3.mix.sort_global_in(received3, alloc3)

    alloc1 = m1.mix.permute_global(pubseed2, 9, nbmix, 0)
    sortout1 = m1.mix.sort_global_out(sortin1, alloc1)

    alloc2 = m2.mix.permute_global(pubseed2, 9, nbmix, 0)
    sortout2 = m2.mix.sort_global_out(sortin2, alloc2)

    alloc3 = m3.mix.permute_global(pubseed2, 9, nbmix, 0)
    sortout3 = m3.mix.sort_global_out(sortin3, alloc3)

    print "----------------------------------------------------------"

    received1 = [sortout1[0], sortout2[0], sortout3[0]]
    received2 = [sortout1[1], sortout2[1], sortout3[1]]
    received3 = [sortout1[2], sortout2[2], sortout3[2]]

    alloc1 = m1.mix.permute_global(pubseed2, 9, nbmix, 0)
    sortin1 = m1.mix.sort_global_in(received1, alloc1)
    sortin1 = m1.mix.permute(seed1, sortin1, 0)
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key1, iv1)
    for i in range(len(sortin1)):
        sortin1[i] = enc.update(sortin1[i])
        sortin1[i] += enc.finalize()

    alloc2 = m2.mix.permute_global(pubseed2, 9, nbmix, 0)
    sortin2 = m2.mix.sort_global_in(received2, alloc2)
    sortin2 = m2.mix.permute(seed2, sortin2, 0)
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key2, iv2)
    for i in range(len(sortin2)):
        sortin2[i] = enc.update(sortin2[i])
        sortin2[i] += enc.finalize()

    alloc3 = m3.mix.permute_global(pubseed2, 9, nbmix, 0)
    sortin3 = m3.mix.sort_global_in(received3, alloc3)
    sortin3 = m3.mix.permute(seed3, sortin3, 0)
    aes = Cipher("AES-128-CTR")
    enc = aes.enc(key3, iv3)
    for i in range(len(sortin3)):
        sortin3[i] = enc.update(sortin3[i])
        sortin3[i] += enc.finalize()

    alloc1 = m1.mix.permute_global(pubseed, 9, nbmix, 0)
    sortout1 = m1.mix.sort_global_out(sortin1, alloc1)

    alloc2 = m2.mix.permute_global(pubseed, 9, nbmix, 0)
    sortout2 = m2.mix.sort_global_out(sortin2, alloc2)

    alloc3 = m3.mix.permute_global(pubseed, 9, nbmix, 0)
    sortout3 = m3.mix.sort_global_out(sortin3, alloc3)

    print "----------------------------------------------------------"

    received1 = [sortout1[0], sortout2[0], sortout3[0]]
    received2 = [sortout1[1], sortout2[1], sortout3[1]]
    received3 = [sortout1[2], sortout2[2], sortout3[2]]

    alloc1 = m1.mix.permute_global(pubseed, 9, nbmix, 0)
    sortin1 = m1.mix.sort_global_in(received1, alloc1)

    alloc2 = m2.mix.permute_global(pubseed, 9, nbmix, 0)
    sortin2 = m2.mix.sort_global_in(received2, alloc2)

    alloc3 = m3.mix.permute_global(pubseed, 9, nbmix, 0)
    sortin3 = m3.mix.sort_global_in(received3, alloc3)

    data = []
    for i in range(9):
        data.append(str(i) * 4)
    res = []
    for i in range(len(sortin1)):
        res.append(sortin1[i])
    for i in range(len(sortin2)):
        res.append(sortin2[i])
    for i in range(len(sortin3)):
        res.append(sortin3[i])

    print "res", res
    print "data", data

    assert data == res
Example #13
0
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)
Example #14
0
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) )