def test_encrypt_decrypt(): msg = b"test yeah" privkey = ecies.generate_privkey() ciphertext = ecies.encrypt(msg, privkey.public_key) decrypted = ecies.decrypt(ciphertext, privkey) assert decrypted == msg privkey2 = ecies.generate_privkey() with pytest.raises(ecies.DecryptionError): decrypted = ecies.decrypt(ciphertext, privkey2)
def decode_ack_plain( ciphertext: bytes, privkey: datatypes.PrivateKey ) -> Tuple[datatypes.PublicKey, bytes, int]: """Decrypts and decodes a legacy pre-EIP-8 auth ack message. Returns the remote's ephemeral pubkey, nonce and protocol version. """ message = ecies.decrypt(ciphertext, privkey) if len(message) != AUTH_ACK_LEN: raise BadAckMessage(f"Unexpected size for ack message: {len(message)}") eph_pubkey = keys.PublicKey(message[:PUBKEY_LEN]) nonce = message[PUBKEY_LEN:PUBKEY_LEN + HASH_LEN] return eph_pubkey, nonce, SUPPORTED_RLPX_VERSION
def decode_auth_plain( ciphertext: bytes, privkey: datatypes.PrivateKey ) -> Tuple[datatypes.Signature, datatypes.PublicKey, bytes, int]: """Decode legacy pre-EIP-8 auth message format""" message = ecies.decrypt(ciphertext, privkey) if len(message) != AUTH_MSG_LEN: raise BadAckMessage(f"Unexpected size for auth message: {len(message)}") signature = keys.Signature(signature_bytes=message[:SIGNATURE_LEN]) pubkey_start = SIGNATURE_LEN + HASH_LEN pubkey = keys.PublicKey(message[pubkey_start : pubkey_start + PUBKEY_LEN]) nonce_start = pubkey_start + PUBKEY_LEN nonce = message[nonce_start : nonce_start + HASH_LEN] return signature, pubkey, nonce, SUPPORTED_RLPX_VERSION
def decode_ack_eip8( ciphertext: bytes, privkey: datatypes.PrivateKey ) -> Tuple[datatypes.PublicKey, bytes, int]: """Decrypts and decodes a EIP-8 auth ack message. Returns the remote's ephemeral pubkey, nonce and protocol version. """ # The length of the actual msg is stored in plaintext on the first two bytes. encoded_size = ciphertext[:2] auth_ack = ciphertext[2:] message = ecies.decrypt(auth_ack, privkey, shared_mac_data=encoded_size) values = rlp.decode(message, sedes=eip8_ack_sedes, strict=False) pubkey_bytes, nonce, version = values[:3] return keys.PublicKey(pubkey_bytes), nonce, version
def test_decrypt_known_good_handshake(): # Data taken from https://gist.github.com/fjl/3a78780d17c755d22df2 privkey = keys.PrivateKey( decode_hex( "c45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8") ) auth_ciphertext = decode_hex( "04a0274c5951e32132e7f088c9bdfdc76c9d91f0dc6078e848f8e3361193dbdc43b94351ea3d89e4ff33ddcefbc80070498824857f499656c4f79bbd97b6c51a514251d69fd1785ef8764bd1d262a883f780964cce6a14ff206daf1206aa073a2d35ce2697ebf3514225bef186631b2fd2316a4b7bcdefec8d75a1025ba2c5404a34e7795e1dd4bc01c6113ece07b0df13b69d3ba654a36e35e69ff9d482d88d2f0228e7d96fe11dccbb465a1831c7d4ad3a026924b182fc2bdfe016a6944312021da5cc459713b13b86a686cf34d6fe6615020e4acf26bf0d5b7579ba813e7723eb95b3cef9942f01a58bd61baee7c9bdd438956b426a4ffe238e61746a8c93d5e10680617c82e48d706ac4953f5e1c4c4f7d013c87d34a06626f498f34576dc017fdd3d581e83cfd26cf125b6d2bda1f1d56" ) # noqa: E501 auth_plaintext = decode_hex( "884c36f7ae6b406637c1f61b2f57e1d2cab813d24c6559aaf843c3f48962f32f46662c066d39669b7b2e3ba14781477417600e7728399278b1b5d801a519aa570034fdb5419558137e0d44cd13d319afe5629eeccb47fd9dfe55cc6089426e46cc762dd8a0636e07a54b31169eba0c7a20a1ac1ef68596f1f283b5c676bae4064abfcce24799d09f67e392632d3ffdc12e3d6430dcb0ea19c318343ffa7aae74d4cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb1100" ) # noqa: E501 decrypted = ecies.decrypt(auth_ciphertext, privkey) assert auth_plaintext == decrypted
def decode_auth_eip8( ciphertext: bytes, privkey: datatypes.PrivateKey ) -> Tuple[datatypes.Signature, datatypes.PublicKey, bytes, int]: """Decode EIP-8 auth message format""" # The length of the actual msg is stored in plaintext on the first two bytes. encoded_size = ciphertext[:2] auth_msg = ciphertext[2:] message = ecies.decrypt(auth_msg, privkey, shared_mac_data=encoded_size) values = rlp.decode(message, sedes=eip8_auth_sedes, strict=False) signature_bytes, pubkey_bytes, nonce, version = values[:4] return ( keys.Signature(signature_bytes=signature_bytes), keys.PublicKey(pubkey_bytes), nonce, version, )
async def test_handshake(): # TODO: this test should be re-written to not depend on functionality in the `ETHPeer` class. cancel_token = CancelToken("test_handshake") use_eip8 = False initiator_remote = kademlia.Node( keys.PrivateKey(test_values["receiver_private_key"]).public_key, kademlia.Address("0.0.0.0", 0, 0), ) initiator = HandshakeInitiator( initiator_remote, keys.PrivateKey(test_values["initiator_private_key"]), use_eip8, cancel_token, ) initiator.ephemeral_privkey = keys.PrivateKey( test_values["initiator_ephemeral_private_key"]) responder_remote = kademlia.Node( keys.PrivateKey(test_values["initiator_private_key"]).public_key, kademlia.Address("0.0.0.0", 0, 0), ) responder = HandshakeResponder( responder_remote, keys.PrivateKey(test_values["receiver_private_key"]), use_eip8, cancel_token, ) responder.ephemeral_privkey = keys.PrivateKey( test_values["receiver_ephemeral_private_key"]) # Check that the auth message generated by the initiator is what we expect. Notice that we # can't use the auth_init generated here because the non-deterministic prefix would cause the # derived secrets to not match the expected values. _auth_init = initiator.create_auth_message(test_values["initiator_nonce"]) assert len(_auth_init) == len(test_values["auth_plaintext"]) assert (_auth_init[65:] == test_values["auth_plaintext"][65:] ) # starts with non deterministic k # Check that encrypting and decrypting the auth_init gets us the orig msg. _auth_init_ciphertext = initiator.encrypt_auth_message(_auth_init) assert _auth_init == ecies.decrypt(_auth_init_ciphertext, responder.privkey) # Check that the responder correctly decodes the auth msg. auth_msg_ciphertext = test_values["auth_ciphertext"] initiator_ephemeral_pubkey, initiator_nonce, _ = decode_authentication( auth_msg_ciphertext, responder.privkey) assert initiator_nonce == test_values["initiator_nonce"] assert initiator_ephemeral_pubkey == (keys.PrivateKey( test_values["initiator_ephemeral_private_key"]).public_key) # Check that the auth_ack msg generated by the responder is what we expect. auth_ack_msg = responder.create_auth_ack_message( test_values["receiver_nonce"]) assert auth_ack_msg == test_values["authresp_plaintext"] # Check that the secrets derived from ephemeral key agreements match the expected values. auth_ack_ciphertext = test_values["authresp_ciphertext"] aes_secret, mac_secret, egress_mac, ingress_mac = responder.derive_secrets( initiator_nonce, test_values["receiver_nonce"], initiator_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext, ) assert aes_secret == test_values["aes_secret"] assert mac_secret == test_values["mac_secret"] # Test values are from initiator perspective, so they're reversed here. assert ingress_mac.digest() == test_values["initial_egress_MAC"] assert egress_mac.digest() == test_values["initial_ingress_MAC"] # Check that the initiator secrets match as well. responder_ephemeral_pubkey, responder_nonce = initiator.decode_auth_ack_message( test_values["authresp_ciphertext"]) ( initiator_aes_secret, initiator_mac_secret, initiator_egress_mac, initiator_ingress_mac, ) = initiator.derive_secrets( initiator_nonce, responder_nonce, responder_ephemeral_pubkey, auth_msg_ciphertext, auth_ack_ciphertext, ) assert initiator_aes_secret == aes_secret assert initiator_mac_secret == mac_secret assert initiator_ingress_mac.digest() == test_values["initial_ingress_MAC"] assert initiator_egress_mac.digest() == test_values["initial_egress_MAC"] # Finally, check that two Peers configured with the secrets generated above understand each # other. ( (responder_reader, responder_writer), (initiator_reader, initiator_writer), ) = get_directly_connected_streams() initiator_connection = PeerConnection( reader=initiator_reader, writer=initiator_writer, aes_secret=initiator_aes_secret, mac_secret=initiator_mac_secret, egress_mac=initiator_egress_mac, ingress_mac=initiator_ingress_mac, ) initiator_peer = ParagonPeer( remote=initiator.remote, privkey=initiator.privkey, connection=initiator_connection, context=ParagonContext(), ) initiator_peer.base_protocol.send_handshake() responder_connection = PeerConnection( reader=responder_reader, writer=responder_writer, aes_secret=aes_secret, mac_secret=mac_secret, egress_mac=egress_mac, ingress_mac=ingress_mac, ) responder_peer = ParagonPeer( remote=responder.remote, privkey=responder.privkey, connection=responder_connection, context=ParagonContext(), ) responder_peer.base_protocol.send_handshake() # The handshake msgs sent by each peer (above) are going to be fed directly into their remote's # reader, and thus the read_msg() calls will return immediately. responder_hello, _ = await responder_peer.read_msg() initiator_hello, _ = await initiator_peer.read_msg() assert isinstance(responder_hello, Hello) assert isinstance(initiator_hello, Hello)