Exemple #1
0
    def verify1(self, credentials, session_pub_key, encrypted):
        """First verification step."""
        # No additional hashing used
        self._shared = self._verify_private.get_shared_key(
            curve25519.Public(session_pub_key), hashfunc=lambda x: x)

        session_key = hkdf_expand('Pair-Verify-Encrypt-Salt',
                                  'Pair-Verify-Encrypt-Info',
                                  self._shared)

        chacha = chacha20.Chacha20Cipher(session_key, session_key)
        decrypted_tlv = tlv8.read_tlv(
            chacha.decrypt(encrypted, nounce='PV-Msg02'.encode()))

        identifier = decrypted_tlv[tlv8.TLV_IDENTIFIER]
        signature = decrypted_tlv[tlv8.TLV_SIGNATURE]

        if identifier != credentials.atv_id:
            raise Exception('incorrect device response')  # TODO: new exception

        info = session_pub_key + \
            bytes(identifier) + self._verify_public.serialize()
        ltpk = VerifyingKey(bytes(credentials.ltpk))
        ltpk.verify(bytes(signature), bytes(info))  # throws if no match

        device_info = self._verify_public.serialize() + \
            credentials.client_id + session_pub_key

        device_signature = SigningKey(credentials.ltsk).sign(device_info)

        tlv = tlv8.write_tlv({tlv8.TLV_IDENTIFIER: credentials.client_id,
                              tlv8.TLV_SIGNATURE: device_signature})

        return chacha.encrypt(tlv, nounce='PV-Msg03'.encode())
Exemple #2
0
    def decrypt_message(self, ct, decryptor):
        decryptor = self.derive_keys(decryptor)

        pt = self.decrypt(ct, decryptor['message_key'], decryptor['message_counter'])
        if not pt:
            return None

        bob_ephemeral, msg = pt[:32], pt[32:]

        self.receive = decryptor

        # it's encrypted with the same key it advertised.. shouldn't happen.
        if bob_ephemeral == self.receive['bob'].serialize():
            raise
        # it's advertising the receive_pending key, we need to respond again because
        # they may not have gotten it.
        elif self.receive_pending \
          and bob_ephemeral == self.receive_pending['bob'].serialize():
            pass
        # yay a new key! we need to respond.
        elif not self.receive_pending \
          or bob_ephemeral != self.receive_pending['bob'].serialize():

            self.receive_pending = {
                'alice': curve25519.Private(),
                'bob': curve25519.Public(bob_ephemeral),
                'receiver': True,
                'acked': False
            }
        # uncharted territory...
        else:
            raise

        return msg
Exemple #3
0
def recover_seed(key='', modulus=None, pos=1):
    # recreate the master private key from the passphrase
    master = curve25519.Private(secret=sha256(key).digest())
    # extract the ephemeral public key from modulus
    ephem_pub = curve25519.Public(modulus[pos:pos + 32])
    # compute seed with master private and ephemeral public
    return (master.get_shared_key(ephem_pub), ephem_pub)
Exemple #4
0
    def __init__(self, node):
        # 5.1.4. The "ntor" handshake

        # This handshake uses a set of DH handshakes to compute a set of
        # shared keys which the client knows are shared only with a particular
        # server, and the server knows are shared with whomever sent the
        # original handshake (or with nobody at all).  Here we use the
        # "curve25519" group and representation as specified in "Curve25519:
        # new Diffie-Hellman speed records" by D. J. Bernstein.

        # [The ntor handshake was added in Tor 0.2.4.8-alpha.]

        self.node = node

        # In this section, define:
        #   H(x,t) as HMAC_SHA256 with message x and key t.
        #   H_LENGTH  = 32.
        #   ID_LENGTH = 20.
        #   G_LENGTH  = 32
        #   PROTOID   = "ntor-curve25519-sha256-1"
        #   t_mac     = PROTOID | ":mac"
        #   t_key     = PROTOID | ":key_extract"
        #   t_verify  = PROTOID | ":verify"
        #   MULT(a,b) = the multiplication of the curve25519 point 'a' by the
        #               scalar 'b'.
        #   G         = The preferred base point for curve25519 ([9])
        #   KEYGEN()  = The curve25519 key generation algorithm, returning
        #               a private/public keypair.
        #   m_expand  = PROTOID | ":key_expand"

        # H is defined as hmac()
        # MULT is included in the curve25519 library as get_shared_key()
        # KEYGEN() is curve25519.Private()
        self.protoid = 'ntor-curve25519-sha256-1'
        self.t_mac = self.protoid + ':mac'
        self.t_key = self.protoid + ':key_extract'
        self.t_verify = self.protoid + ':verify'
        self.m_expand = self.protoid + ':key_expand'

        # To perform the handshake, the client needs to know an identity key
        # digest for the server, and an ntor onion key (a curve25519 public
        # key) for that server. Call the ntor onion key "B".  The client
        # generates a temporary keypair:
        #     x,X = KEYGEN()
        self.x = curve25519.Private()
        self.X = self.x.get_public()

        self.B = curve25519.Public(b64decode(self.node['ntor-onion-key']))

        # and generates a client-side handshake with contents:
        #   NODEID      Server identity digest  [ID_LENGTH bytes]
        #   KEYID       KEYID(B)                [H_LENGTH bytes]
        #   CLIENT_PK   X                       [G_LENGTH bytes]
        self.handshake = b64decode(self.node['identity'])
        self.handshake += self.B.serialize()
        self.handshake += self.X.serialize()
def setkey(data, server, witem):
    data = data.split(' ')

    try:
        keys[data[0]] = curve25519.Public(b64decode(data[1]))
        save_keys()
    except:
        print 'Usage: /setkey <nick> <key>.'

    print 'Set key for %s to: %s' % (data[0], data[1])
    return
Exemple #6
0
    def setConnInfoParams(self, secret):
        self.secret = secret
        self.sharedSecret = self.privateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a: a)
        sharedSecretExpanded = HKDF(self.sharedSecret, 80)
        hmacValidation = HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:])
        if hmacValidation != secret[32:64]:
            raise ValueError("Hmac mismatch")

        keysEncrypted = sharedSecretExpanded[64:] + secret[64:]
        keysDecrypted = AESDecrypt(sharedSecretExpanded[:32], keysEncrypted)
        self.encKey = keysDecrypted[:32]
        self.macKey = keysDecrypted[32:64]
Exemple #7
0
    def _seqno_1(self, pairing_data):
        if self.has_paired:
            server_pub_key = self._verify_public.serialize()
            client_pub_key = pairing_data[tlv8.TLV_PUBLIC_KEY]

            self._shared = self._verify_private.get_shared_key(
                curve25519.Public(client_pub_key), hashfunc=lambda x: x)

            session_key = hkdf_expand('Pair-Verify-Encrypt-Salt',
                                      'Pair-Verify-Encrypt-Info', self._shared)

            info = server_pub_key + self.atv_device_id + client_pub_key
            signature = SigningKey(self._signing_key.to_seed()).sign(info)

            tlv = tlv8.write_tlv({
                tlv8.TLV_IDENTIFIER: self.atv_device_id,
                tlv8.TLV_SIGNATURE: signature
            })

            chacha = chacha20.Chacha20Cipher(session_key, session_key)
            encrypted = chacha.encrypt(tlv, nounce='PV-Msg02'.encode())

            msg = messages.crypto_pairing({
                tlv8.TLV_SEQ_NO: b'\x02',
                tlv8.TLV_PUBLIC_KEY: server_pub_key,
                tlv8.TLV_ENCRYPTED_DATA: encrypted
            })

            self.output_key = hkdf_expand('MediaRemote-Salt',
                                          'MediaRemote-Write-Encryption-Key',
                                          self._shared)

            self.input_key = hkdf_expand('MediaRemote-Salt',
                                         'MediaRemote-Read-Encryption-Key',
                                         self._shared)

            log_binary(_LOGGER,
                       'Keys',
                       Output=self.output_key,
                       Input=self.input_key)

        else:
            msg = messages.crypto_pairing({
                tlv8.TLV_SALT:
                binascii.unhexlify(self.salt),
                tlv8.TLV_PUBLIC_KEY:
                binascii.unhexlify(self._session.public),
                tlv8.TLV_SEQ_NO:
                b'\x02'
            })

        self._send(msg)
def load_keys():
    try:
        tmp_keys = json.load(open(key_path))
        fix_key_perms()
    except:
        keys['my_key'] = curve25519.Private()
        save_keys()
        return

    for key in tmp_keys:
        if key == 'my_key':
            keys[key] = curve25519.Private(b64decode(tmp_keys[key]))
        else:
            keys[key] = curve25519.Public(b64decode(tmp_keys[key]))
Exemple #9
0
    async def _generate_final_keys(self, secret):
        self.secret = base64.b64decode(secret)
        self.shared_secret = self.private.get_shared_key(
            curve25519.Public(self.secret[:32]), lambda a: a)
        self.shared_secret_expended = hkdf(80, self.shared_secret)

        if not self._is_final_valid():
            raise ValueError('hmac final keys mismatch')

        enc_key = self.shared_secret_expended[64:] + self.secret[64:]
        denc_key = AESDecrypt(self.shared_secret_expended[:32], enc_key)

        self.enc_key = denc_key[:32]
        self.mac_key = denc_key[32:64]
        self._ready.set()
Exemple #10
0
    def _pair_verify_one(self, tlv_objects):
        """Generate new session key pair and send a proof to the client.

        @param tlv_objects: The TLV data received from the client.
        @type tlv_object: dict
        """
        logger.debug("Pair verify [1/2].")
        client_public = tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY]

        private_key = curve25519.Private()
        public_key = private_key.get_public()
        shared_key = private_key.get_shared_key(
            curve25519.Public(client_public),
            # Key is hashed before being returned, we don't want it; This fixes that.
            lambda x: x,
        )

        mac = self.state.mac.encode()
        material = public_key.serialize() + mac + client_public
        server_proof = self.state.private_key.sign(material)

        output_key = hap_hkdf(shared_key, self.PVERIFY_1_SALT,
                              self.PVERIFY_1_INFO)

        self._set_encryption_ctx(client_public, private_key, public_key,
                                 shared_key, output_key)

        message = tlv.encode(HAP_TLV_TAGS.USERNAME, mac, HAP_TLV_TAGS.PROOF,
                             server_proof)

        cipher = ChaCha20Poly1305(output_key)
        aead_message = bytes(
            cipher.encrypt(self.PVERIFY_1_NONCE, bytes(message), b""))
        data = tlv.encode(
            HAP_TLV_TAGS.SEQUENCE_NUM,
            b"\x02",
            HAP_TLV_TAGS.ENCRYPTED_DATA,
            aead_message,
            HAP_TLV_TAGS.PUBLIC_KEY,
            public_key.serialize(),
        )
        self.send_response(200)
        self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE)
        self.end_response(data)
Exemple #11
0
    def __init__(self, ref_dict, priv_key_list, pub_key_list):

        self.conn_data = {
            "private_key": None,
            "public_key": None,
            "secret": None,
            "shared_secret": None,
            "shared_secret_ex": None,
            "aes_key": None,
            "mac_key": None
        }

        self.conn_data["private_key"] = curve25519.Private("".join(
            [chr(x) for x in priv_key_list]))
        self.conn_data["public_key"] = self.conn_data[
            "private_key"].get_public()

        assert (self.conn_data["public_key"].serialize() == "".join(
            [chr(x) for x in pub_key_list]))

        self.conn_data["secret"] = base64.b64decode(ref_dict["secret"])
        self.conn_data["shared_secret"] = self.conn_data[
            "private_key"].get_shared_key(
                curve25519.Public(self.conn_data["secret"][:32]),
                lambda key: key)

        shared_expended = self.conn_data["shared_secret_ex"] = HKDF(
            self.conn_data["shared_secret"], 80)

        check_hmac = hmac_sha256(
            shared_expended[32:64],
            self.conn_data["secret"][:32] + self.conn_data["secret"][64:])

        if check_hmac != self.conn_data["secret"][32:64]:
            raise ValueError("Error hmac mismatch")

        keysDecrypted = AESDecrypt(
            shared_expended[:32],
            shared_expended[64:] + self.conn_data["secret"][64:])

        self.conn_data["aes_key"] = keysDecrypted[:32]
        self.conn_data["mac_key"] = keysDecrypted[32:64]
Exemple #12
0
    def verify2(self, atv_public_key, data):
        """
        Last device verification step.
        """
        # Generate a shared secret key
        public = curve25519.Public(atv_public_key)
        shared = self._verify_private.get_shared_key(
            public, hashfunc=lambda x: x)  # No additional hashing used

        # Derive new AES key and IV from shared key
        aes_key = hash_sha512('Pair-Verify-AES-Key', shared)[0:16]
        aes_iv = hash_sha512('Pair-Verify-AES-IV', shared)[0:16]

        # Sign public keys and encrypt with AES
        signer = SigningKey(self._auth_private)
        signed = signer.sign(self._verify_public.serialize() + atv_public_key)
        signature, _ = aes_encrypt(modes.CTR, aes_key, aes_iv, data, signed)

        # Signature is prepended with 0x00000000 (alignment?)
        return b'\x00\x00\x00\x00' + signature
Exemple #13
0
    def _seqno1_paired(self, pairing_data):
        server_pub_key = self.keys.verify_pub.serialize()
        client_pub_key = pairing_data[tlv8.TLV_PUBLIC_KEY]

        shared_key = self.keys.verify.get_shared_key(
            curve25519.Public(client_pub_key), hashfunc=lambda x: x)

        session_key = hkdf_expand('Pair-Verify-Encrypt-Salt',
                                  'Pair-Verify-Encrypt-Info', shared_key)

        info = server_pub_key + self.unique_id + client_pub_key
        signature = self.keys.sign.sign(info)

        tlv = tlv8.write_tlv({
            tlv8.TLV_IDENTIFIER: self.unique_id,
            tlv8.TLV_SIGNATURE: signature
        })

        chacha = chacha20.Chacha20Cipher(session_key, session_key)
        encrypted = chacha.encrypt(tlv, nounce='PV-Msg02'.encode())

        msg = messages.crypto_pairing({
            tlv8.TLV_SEQ_NO: b'\x02',
            tlv8.TLV_PUBLIC_KEY: server_pub_key,
            tlv8.TLV_ENCRYPTED_DATA: encrypted
        })

        self.output_key = hkdf_expand('MediaRemote-Salt',
                                      'MediaRemote-Write-Encryption-Key',
                                      shared_key)

        self.input_key = hkdf_expand('MediaRemote-Salt',
                                     'MediaRemote-Read-Encryption-Key',
                                     shared_key)

        log_binary(_LOGGER,
                   'Keys',
                   Output=self.output_key,
                   Input=self.input_key)
        self.delegate.send(msg)
Exemple #14
0
    def _pair_verify_one(self, tlv_objects):
        """Generate new session key pair and send a proof to the client.

        @param tlv_objects: The TLV data received from the client.
        @type tlv_object: dict
        """
        logging.debug("Pair verify [1/2].")
        client_public = tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY]

        private_key = curve25519.Private()
        public_key = private_key.get_public()
        shared_key = private_key.get_shared_key(
            curve25519.Public(client_public),
            # Key is hashed before being returned, we don't want it; This fixes that.
            lambda x: x)

        mac = self._state.mac.encode()
        material = public_key.serialize() + mac + client_public
        server_proof = self._state.private_key.sign(material)

        output_key = hap_hkdf(shared_key, PVERIFY_1_SALT, PVERIFY_1_INFO)

        enc_context = {
            "client_public": client_public,
            "private_key": private_key,
            "public_key": public_key,
            "shared_key": shared_key,
            "pre_session_key": output_key
        }

        message = tlv.encode(HAP_TLV_TAGS.USERNAME, mac, HAP_TLV_TAGS.PROOF,
                             server_proof)

        cipher = CHACHA20_POLY1305(output_key, "python")
        aead_message = bytes(
            cipher.seal(PVERIFY_1_NONCE, bytearray(message), b""))
        data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x02',
                          HAP_TLV_TAGS.ENCRYPTED_DATA, aead_message,
                          HAP_TLV_TAGS.PUBLIC_KEY, public_key.serialize())
        return data, enc_context
Exemple #15
0
   def _pair_verify_one(self, tlv_objects):
      """Generate new session key pair and send a proof to the client.

      @param tlv_objects: The TLV data received from the client.
      @type tlv_object: dict
      """
      logger.debug("Pair verify [1/2].")
      client_public = tlv_objects[HAP_TLV_TAGS.PUBLIC_KEY]

      private_key = curve25519.Private()
      public_key = private_key.get_public()
      shared_key = private_key.get_shared_key(
                        curve25519.Public(client_public),
            # Why not randomly hash the key before returning it
            # so that it is incompatible with another
            # implementation and force clients to use hacks?
                        lambda x: x)

      mac = self.accessory.mac.encode()
      material = public_key.serialize() + mac + client_public
      server_proof = self.accessory.private_key.sign(material)

      output_key = hap_hkdf(shared_key, self.PVERIFY_1_SALT, self.PVERIFY_1_INFO)

      self._set_encryption_ctx(client_public, private_key, public_key,
                               shared_key, output_key)

      message = tlv.encode(HAP_TLV_TAGS.USERNAME, mac,
                           HAP_TLV_TAGS.PROOF, server_proof)

      cipher = CHACHA20_POLY1305(output_key, "python")
      aead_message = bytes(
         cipher.seal(self.PVERIFY_1_NONCE, bytearray(message), b""))
      data = tlv.encode(HAP_TLV_TAGS.SEQUENCE_NUM, b'\x02',
                        HAP_TLV_TAGS.ENCRYPTED_DATA, aead_message,
                        HAP_TLV_TAGS.PUBLIC_KEY, public_key.serialize())
      self.send_response(200)
      self.send_header("Content-Type", self.PAIRING_RESPONSE_TYPE)
      self.end_response(data)
Exemple #16
0
def get_enc_mac_keys(encoded_secret, privateKey):
    secret = b64decode(encoded_secret)
    if len(secret) != 144:
        raise ValueError('Invalid secret size')

    sharedSecret = privateKey.get_shared_key(curve25519.Public(secret[:32]),
                                             lambda a: a)

    key_material = Hmac(b'\0' * 32).hash(sharedSecret)
    sharedSecretExpanded = hkdf_expand(key_material,
                                       length=80,
                                       hash=hashlib.sha256)
    if not Hmac(sharedSecretExpanded[32:64]).is_valid(
            secret[:32] + secret[64:], expected=secret[32:64]):
        raise ValueError('Hmac validation failed')

    keysEncrypted = sharedSecretExpanded[64:] + secret[64:]
    keysDecrypted = Aes(sharedSecretExpanded[:32]).decrypt(keysEncrypted)

    encKey = keysDecrypted[:32]
    macKey = keysDecrypted[32:64]

    return encKey, macKey
    def __init__(self, ref_dict, priv_key_list, pub_key_list):
        self.decrypted_serialized_content = None
        self.message_tag = None
        self.decrypted_content = None
        self.original_content = None

        self.private_key = None
        self.public_key = None
        self.secret = None
        self.shared_secret = None
        self.aes_key = None
        self.mac_key = None

        self.private_key = curve25519.Private("".join(
            [chr(x) for x in priv_key_list]))
        self.public_key = self.private_key.get_public()

        assert (self.public_key.serialize() == "".join(
            [chr(x) for x in pub_key_list]))

        self.secret = base64.b64decode(ref_dict["secret"])
        self.shared_secret = self.private_key.get_shared_key(
            curve25519.Public(self.secret[:32]), lambda key: key)

        shared_expended = HKDF(self.shared_secret, 80)

        check_hmac = hmac_sha256(shared_expended[32:64],
                                 self.secret[:32] + self.secret[64:])

        if check_hmac != self.secret[32:64]:
            raise ValueError("Error hmac mismatch")

        keys_decrypted = aes_decrypt(shared_expended[:32],
                                     shared_expended[64:] + self.secret[64:])

        self.aes_key = keys_decrypted[:32]
        self.mac_key = keys_decrypted[32:64]
Exemple #18
0

if __name__ == "__main__":
    # passphrase and filename as arguments
    if len(sys.argv) == 3:
        # Load an x.509 certificate from a file
        x509 = X509.load_cert(sys.argv[2])
        # Pull the modulus out of the certificate
        orig_modulus = unhexlify(x509.get_pubkey().get_modulus())
        (seed, ephem_pub) = recover_seed(key=sys.argv[1],
                                         modulus=orig_modulus,
                                         pos=80)
    # no arguments, just generate a private key
    else:
        # deserialize master ECDH public key embedded in program
        master_pub = curve25519.Public(unhexlify(MASTER_PUB_HEX))
        # generate a random (yes, actually random) ECDH private key
        ephem = curve25519.Private()
        # derive the corresponding public key for later embedding
        ephem_pub = ephem.get_public()
        # combine the ECDH keys to generate the seed
        seed = ephem.get_shared_key(master_pub)

    prng = AESPRNG(seed)
    ephem_pub = ephem_pub.serialize()

    # deterministic key generation from seed
    rsa = build_key(embed=ephem_pub, pos=80, randfunc=prng.randbytes)

    if 'orig_modulus' in locals():
        if long(hexlify(orig_modulus), 16) != long(rsa.n):
Exemple #19
0
 def receive_key(self, bob_ephemeral):
     self.receive['bob'] = curve25519.Public(bob_ephemeral)
     if 'counter' in self.receive:
         del self.receive['counter']
Exemple #20
0
 def send_key(self, bob_ephemeral):
     self.send['bob'] = curve25519.Public(bob_ephemeral)
     if 'counter' in self.send:
         del self.send['counter']
Exemple #21
0
    def pairVerifyStep1(self, tlvData):
        publicKey = tlvData['public_key']['data']

        accessoryLTSK = ed25519.SigningKey(self.longTermKey, encoding='hex')
        # accessoryLTPK = accessoryLTSK.get_verifying_key()

        accessoryPairingID = HapHandler.getId()

        iosDevicePublicKey = curve25519.Public(''.join(
            [chr(x) for x in publicKey]))

        # Step 1
        private = curve25519.Private()
        public = private.get_public()
        publicSerialized = public.serialize()

        # Step 2
        shared = private.get_shared_key(iosDevicePublicKey,
                                        hashfunc=lambda x: x)

        # Step 3
        accessoryPairingInfo = publicSerialized + accessoryPairingID + iosDevicePublicKey.serialize(
        )

        # Step 4
        accessorySignature = accessoryLTSK.sign(accessoryPairingInfo)

        # Step 5
        response = []
        response.append({
            'type': 'identifier',
            'length': len(accessoryPairingID),
            'data': accessoryPairingID
        })
        response.append({
            'type': 'signature',
            'length': len(accessorySignature),
            'data': accessorySignature
        })
        subTLV = pack(response)  # tlv

        # Step 6
        inputKey = shared
        salt = 'Pair-Verify-Encrypt-Salt'
        info = 'Pair-Verify-Encrypt-Info'

        key = Hkdf(salt, inputKey, hash=hashlib.sha512)
        sessionKey = key.expand(info, length=32)

        key = Hkdf('Control-Salt', shared, hash=hashlib.sha512)
        writeKey = key.expand('Control-Write-Encryption-Key', length=32)

        key = Hkdf('Control-Salt', shared, hash=hashlib.sha512)
        readKey = key.expand('Control-Read-Encryption-Key', length=32)

        self.sessionStorage = {
            'clientPublicKey': iosDevicePublicKey.serialize(),
            'secretKey': private.serialize(),
            'publicKey': publicSerialized,
            'sharedSec': shared,
            'hkdfPairEncKey': sessionKey,
            'writeKey': writeKey,
            'readKey': readKey
        }

        # Step 7
        ciphertext, mac = HapHandler.encryptAndSeal(sessionKey, 'PV-Msg02',
                                                    [ord(x) for x in subTLV])

        self.sendTLVResponse([
            {
                'type': 'state',
                'length': 1,
                'data': 2
            },
            {
                'type': 'public_key',
                'length': len(publicSerialized),
                'data': publicSerialized
            },
            {
                'type': 'encrypted_data',
                'length': len(ciphertext + mac),
                'data': ciphertext + mac
            },
        ])
Exemple #22
0
        def get_shared(self, public):
            public = curve25519.Public(public)

            return self._private.get_shared_key(public, hashfunc=lambda x: x)
Exemple #23
0
    def complete_handshake(self, Y, auth):
        # The server's handshake reply is:
        # SERVER_PK   Y                       [G_LENGTH bytes]
        # AUTH        H(auth_input, t_mac)    [H_LENGTH bytes]

        # The client then checks Y is in G^* [see NOTE below], and computes

        # secret_input = EXP(Y,x) | EXP(B,x) | ID | B | X | Y | PROTOID
        si = self.x.get_shared_key(curve25519.Public(Y), hash_func)
        si += self.x.get_shared_key(self.B, hash_func)
        si += b64decode(self.node['identity'])
        si += self.B.serialize()
        si += self.X.serialize()
        si += Y
        si += 'ntor-curve25519-sha256-1'

        # KEY_SEED = H(secret_input, t_key)
        # verify = H(secret_input, t_verify)
        key_seed = hmac(self.t_key, si)
        verify = hmac(self.t_verify, si)

        # auth_input = verify | ID | B | Y | X | PROTOID | "Server"
        ai = verify
        ai += b64decode(self.node['identity'])
        ai += self.B.serialize()
        ai += Y
        ai += self.X.serialize()
        ai += self.protoid
        ai += 'Server'

        # The client verifies that AUTH == H(auth_input, t_mac).
        if auth != hmac(self.t_mac, ai):
            raise NtorError('auth input does not match.')

        # Both parties check that none of the EXP() operations produced the
        # point at infinity. [NOTE: This is an adequate replacement for
        # checking Y for group membership, if the group is curve25519.]

        # Both parties now have a shared value for KEY_SEED.  They expand this
        # into the keys needed for the Tor relay protocol, using the KDF
        # described in 5.2.2 and the tag m_expand.

        # 5.2.2. KDF-RFC5869

        # For newer KDF needs, Tor uses the key derivation function HKDF from
        # RFC5869, instantiated with SHA256.  (This is due to a construction
        # from Krawczyk.)  The generated key material is:

        #     K = K_1 | K_2 | K_3 | ...

        #     Where H(x,t) is HMAC_SHA256 with value x and key t
        #       and K_1     = H(m_expand | INT8(1) , KEY_SEED )
        #       and K_(i+1) = H(K_i | m_expand | INT8(i+1) , KEY_SEED )
        #       and m_expand is an arbitrarily chosen value,
        #       and INT8(i) is a octet with the value "i".

        # In RFC5869's vocabulary, this is HKDF-SHA256 with info == m_expand,
        # salt == t_key, and IKM == secret_input.
        keys = hkdf(key_seed, length=72, info=self.m_expand)

        # When used in the ntor handshake, the first HASH_LEN bytes form the
        # forward digest Df; the next HASH_LEN form the backward digest Db; the
        # next KEY_LEN form Kf, the next KEY_LEN form Kb, and the final
        # DIGEST_LEN bytes are taken as a nonce to use in the place of KH in the
        # hidden service protocol.  Excess bytes from K are discarded.
        Df, Db, Kf, Kb = struct.unpack('>20s20s16s16s', keys)

        # we do what we can with what we've got.
        del self.X
        del self.x
        del self.B
        del key_seed
        del keys
        del verify
        del ai
        del auth
        del si
        del Y

        self.send_digest = Hash(SHA1(), backend=bend)
        self.send_digest.update(Df)
        self.recv_digest = Hash(SHA1(), backend=bend)
        self.recv_digest.update(Db)

        self.encrypt = Cipher(AES(Kf), CTR('\x00' * 16),
                              backend=bend).encryptor()
        self.decrypt = Cipher(AES(Kb), CTR('\x00' * 16),
                              backend=bend).decryptor()