def derive_secrets( self, initiator_nonce: bytes, responder_nonce: bytes, remote_ephemeral_pubkey: datatypes.PublicKey, auth_init_ciphertext: bytes, auth_ack_ciphertext: bytes ) -> Tuple[bytes, bytes, PreImage, PreImage]: """Derive base secrets from ephemeral key agreement.""" # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) ecdhe_shared_secret = ecies.ecdh_agree(self.ephemeral_privkey, remote_ephemeral_pubkey) # shared-secret = keccak(ecdhe-shared-secret || keccak(nonce || initiator-nonce)) shared_secret = keccak(ecdhe_shared_secret + keccak(responder_nonce + initiator_nonce)) # aes-secret = keccak(ecdhe-shared-secret || shared-secret) aes_secret = keccak(ecdhe_shared_secret + shared_secret) # mac-secret = keccak(ecdhe-shared-secret || aes-secret) mac_secret = keccak(ecdhe_shared_secret + aes_secret) # setup keccak instances for the MACs # egress-mac = keccak_with_digest.new(mac-secret ^ recipient-nonce || auth-sent-init) mac1 = keccak_with_digest.new( sxor(mac_secret, responder_nonce) + auth_init_ciphertext) # ingress-mac = keccak_with_digest.new(mac-secret ^ initiator-nonce || auth-recvd-ack) mac2 = keccak_with_digest.new( sxor(mac_secret, initiator_nonce) + auth_ack_ciphertext) if self._is_initiator: egress_mac, ingress_mac = mac1, mac2 else: egress_mac, ingress_mac = mac2, mac1 return aes_secret, mac_secret, egress_mac, ingress_mac
def create_auth_message(self, nonce: bytes) -> bytes: ecdh_shared_secret = ecies.ecdh_agree(self.privkey, self.remote.pubkey) secret_xor_nonce = sxor(ecdh_shared_secret, nonce) # S(ephemeral-privk, ecdh-shared-secret ^ nonce) S = self.ephemeral_privkey.sign_msg_hash(secret_xor_nonce).to_bytes() # S || H(ephemeral-pubk) || pubk || nonce || 0x0 return (S + keccak(self.ephemeral_pubkey.to_bytes()) + self.pubkey.to_bytes() + nonce + b'\x00')
def create_auth_message(self, nonce: bytes) -> bytes: ecdh_shared_secret = ecies.ecdh_agree(self.privkey, self.remote.pubkey) secret_xor_nonce = sxor(ecdh_shared_secret, nonce) S = self.ephemeral_privkey.sign_msg_hash(secret_xor_nonce).to_bytes() if self.use_eip8: data = rlp.encode([S, self.pubkey.to_bytes(), nonce, DEVP2P_V4], sedes=eip8_auth_sedes) return _pad_eip8_data(data) else: # S || H(ephemeral-pubk) || pubk || nonce || 0x0 return (S + keccak(self.ephemeral_pubkey.to_bytes()) + self.pubkey.to_bytes() + nonce + b'\x00')
def create_auth_message(self, nonce: bytes) -> bytes: ecdh_shared_secret = ecies.ecdh_agree(self.privkey, self.remote.pubkey) secret_xor_nonce = sxor(ecdh_shared_secret, nonce) # S(ephemeral-privk, ecdh-shared-secret ^ nonce) S = self.ephemeral_privkey.sign_msg_hash(secret_xor_nonce).to_bytes() # S || H(ephemeral-pubk) || pubk || nonce || 0x0 return ( S + keccak(self.ephemeral_pubkey.to_bytes()) + self.pubkey.to_bytes() + nonce + b'\x00' )
def compute_session_keys(cls, *, local_private_key: bytes, remote_public_key: bytes, local_node_id: NodeID, remote_node_id: NodeID, id_nonce: IDNonce, is_locally_initiated: bool) -> SessionKeys: local_private_key_object = PrivateKey(local_private_key) remote_public_key_object = PublicKey.from_compressed_bytes( remote_public_key) secret = ecdh_agree(local_private_key_object, remote_public_key_object) if is_locally_initiated: initiator_node_id, recipient_node_id = local_node_id, remote_node_id else: initiator_node_id, recipient_node_id = remote_node_id, local_node_id info = b"".join(( HKDF_INFO, initiator_node_id, recipient_node_id, )) hkdf = HKDF( algorithm=SHA256(), length=3 * AES128_KEY_SIZE, salt=id_nonce, info=info, backend=cls.cryptography_backend, ) expanded_key = hkdf.derive(secret) if len(expanded_key) != 3 * AES128_KEY_SIZE: raise Exception( "Invariant: Secret is expanded to three AES128 keys") initiator_key = expanded_key[:AES128_KEY_SIZE] recipient_key = expanded_key[AES128_KEY_SIZE:2 * AES128_KEY_SIZE] auth_response_key = expanded_key[2 * AES128_KEY_SIZE:3 * AES128_KEY_SIZE] if is_locally_initiated: encryption_key, decryption_key = initiator_key, recipient_key else: encryption_key, decryption_key = recipient_key, initiator_key return SessionKeys( encryption_key=AES128Key(encryption_key), decryption_key=AES128Key(decryption_key), auth_response_key=AES128Key(auth_response_key), )
def derive_secrets(self, initiator_nonce: bytes, responder_nonce: bytes, remote_ephemeral_pubkey: datatypes.PublicKey, auth_init_ciphertext: bytes, auth_ack_ciphertext: bytes ) -> Tuple[bytes, bytes, PreImage, PreImage]: """Derive base secrets from ephemeral key agreement.""" # ecdhe-shared-secret = ecdh.agree(ephemeral-privkey, remote-ephemeral-pubk) ecdhe_shared_secret = ecies.ecdh_agree( self.ephemeral_privkey, remote_ephemeral_pubkey) # shared-secret = keccak(ecdhe-shared-secret || keccak(nonce || initiator-nonce)) shared_secret = keccak( ecdhe_shared_secret + keccak(responder_nonce + initiator_nonce)) # aes-secret = keccak(ecdhe-shared-secret || shared-secret) aes_secret = keccak(ecdhe_shared_secret + shared_secret) # mac-secret = keccak(ecdhe-shared-secret || aes-secret) mac_secret = keccak(ecdhe_shared_secret + aes_secret) # setup keccak instances for the MACs # egress-mac = keccak_with_digest.new(mac-secret ^ recipient-nonce || auth-sent-init) mac1 = keccak_with_digest.new( sxor(mac_secret, responder_nonce) + auth_init_ciphertext ) # ingress-mac = keccak_with_digest.new(mac-secret ^ initiator-nonce || auth-recvd-ack) mac2 = keccak_with_digest.new( sxor(mac_secret, initiator_nonce) + auth_ack_ciphertext ) if self._is_initiator: egress_mac, ingress_mac = mac1, mac2 else: egress_mac, ingress_mac = mac2, mac1 return aes_secret, mac_secret, egress_mac, ingress_mac
def decode_authentication(self, ciphertext: bytes) -> Tuple[datatypes.PublicKey, bytes]: """Decrypts and decodes the auth_init message. Returns the initiator's ephemeral pubkey and nonce. """ if len(ciphertext) < ENCRYPTED_AUTH_MSG_LEN: raise ValueError("Auth msg too short: {}".format(len(ciphertext))) elif len(ciphertext) == ENCRYPTED_AUTH_MSG_LEN: sig, initiator_pubkey, initiator_nonce, _ = decode_auth_plain( ciphertext, self.privkey) else: sig, initiator_pubkey, initiator_nonce, _ = decode_auth_eip8( ciphertext, self.privkey) self.got_eip8_auth = True # recover initiator ephemeral pubkey from sig # S(ephemeral-privk, ecdh-shared-secret ^ nonce) shared_secret = ecies.ecdh_agree(self.privkey, initiator_pubkey) ephem_pubkey = sig.recover_public_key_from_msg_hash( sxor(shared_secret, initiator_nonce)) return ephem_pubkey, initiator_nonce
def decode_authentication( ciphertext: bytes, privkey: datatypes.PrivateKey ) -> Tuple[datatypes.PublicKey, bytes, datatypes.PublicKey]: """ Decrypts and decodes the ciphertext msg. Returns the initiator's ephemeral pubkey, nonce, and pubkey. """ if len(ciphertext) < ENCRYPTED_AUTH_MSG_LEN: raise DecryptionError("Auth msg too short: {}".format(len(ciphertext))) elif len(ciphertext) == ENCRYPTED_AUTH_MSG_LEN: sig, initiator_pubkey, initiator_nonce, _ = decode_auth_plain( ciphertext, privkey) else: sig, initiator_pubkey, initiator_nonce, _ = decode_auth_eip8( ciphertext, privkey) # recover initiator ephemeral pubkey from sig # S(ephemeral-privk, ecdh-shared-secret ^ nonce) shared_secret = ecies.ecdh_agree(privkey, initiator_pubkey) ephem_pubkey = sig.recover_public_key_from_msg_hash( sxor(shared_secret, initiator_nonce)) return ephem_pubkey, initiator_nonce, initiator_pubkey
def test_ecdh(privkey_hex, pubkey_hex, ecdh_expected): privkey = keys.PrivateKey(decode_hex(privkey_hex)) pubkey = keys.PublicKey(decode_hex(pubkey_hex)) assert ecdh_expected == encode_hex(ecies.ecdh_agree(privkey, pubkey))