6.9 ECDH LEFT TO THE READER

EXERCISE 6.9 ECDH LEFT TO THE READER

We did not show code for verifying the public parameters received in the AuthenticatedECDHExchange class. Luckily for you, we’ve left it as an exercise to the reader! Update the generate_session_key method to be generate_authenticated_session_key. This method should implement the algorithm previously described for getting the signature length, verifying the signature using a public key, and then deriving the session keys.


from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
import struct 

class AuthenticatedECDHExchange:
    def __init__(self, curve: ec.EllipticCurve, my_auth_private_key: ec.EllipticCurvePrivateKey, peers_auth_public_key: ec.EllipticCurvePublicKey):
        self._curve = curve

        # Generate an ephemeral private key for use in the exchange.
        self._private_key = ec.generate_private_key(
            curve, default_backend())

        self.enc_key = None
        self.mac_key = None

        # long term keys used for authentication.
        self._my_auth_private_key = my_auth_private_key
        self._peers_auth_public_key = peers_auth_public_key 

    def get_signed_public_bytes(self) -> bytes:
        public_key = self._private_key.public_key()

        raw_bytes = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo)
        
        # This is a signature to prove who we are. 
        signature = self._my_auth_private_key.sign(
            data = raw_bytes, 
            signature_algorithm=ec.ECDSA(hashes.SHA256())
        )

        return struct.pack('I', len(signature)) + raw_bytes + signature 

    def generate_authenticated_session_key(self, signed_peer_bytes: bytes) -> None:
        # let l be the length of I, unsigned int, probably equal to 4.
        l = struct.calcsize('I')

        len_of_signature, = struct.unpack('I', signed_peer_bytes[:l])
        peer_bytes, signature = signed_peer_bytes[l:(-len_of_signature)], signed_peer_bytes[(-len_of_signature):]

        self._peers_auth_public_key.verify(
            signature=signature,
            data=peer_bytes, 
            signature_algorithm=ec.ECDSA(hashes.SHA256()),
        )

        peer_public_key = serialization.load_pem_public_key(
            peer_bytes,
            backend=default_backend())
        shared_key = self._private_key.exchange(
            ec.ECDH(),
            peer_public_key)

        # derive 64 bytes of key material for 2 32-byte keys
        key_material = HKDF(
            algorithm=hashes.SHA256(),
            length=64,
            salt=None,
            info=None,
            backend=default_backend()).derive(shared_key)

        # get the encryption key
        self.enc_key = key_material[:32]

        # derive an MAC key
        self.mac_key = key_material[32:64]

Let’s generate a private and public ECDSA keys for Alice.

alice_private_key = ec.generate_private_key(curve=ec.SECP384R1(), backend=default_backend())
alice_public_key = alice_private_key.public_key()

Let’s do the same thing for Bob.

bob_private_key = ec.generate_private_key(curve=ec.SECP384R1(), backend=default_backend())
bob_public_key = bob_private_key.public_key()

Assume they know each other’s public key. That is, Alice knows Bob’s public key and vice versa.

Now, let them exchange keys with ECDH:

alice_ecdh = AuthenticatedECDHExchange(
    curve=ec.SECP384R1(), 
    my_auth_private_key=alice_private_key, 
    peers_auth_public_key=bob_public_key,
)
bob_ecdh = AuthenticatedECDHExchange(
    curve=ec.SECP384R1(),
    my_auth_private_key=bob_private_key, 
    peers_auth_public_key=alice_public_key,
)
alice_ecdh.generate_authenticated_session_key(bob_ecdh.get_signed_public_bytes())
bob_ecdh.generate_authenticated_session_key(alice_ecdh.get_signed_public_bytes())
if alice_ecdh.enc_key and alice_ecdh.mac_key and alice_ecdh.enc_key == bob_ecdh.enc_key and alice_ecdh.mac_key == bob_ecdh.mac_key:
    print("[PASS]")
else:
    print("[FAIL]")
[PASS]
print("SessionKeys")
print("-----------")
print(f"Encryption Key: {alice_ecdh.enc_key.hex(' ')}")
print(f"MAC Key: {alice_ecdh.mac_key.hex(' ')}")
SessionKeys
-----------
Encryption Key: 4b fe 71 65 d2 29 68 39 94 53 4d 9c 47 5b 62 24 ab 14 3e f5 d0 1a c0 22 fc cd f0 b0 08 9d ff 80
MAC Key: ff 91 1e 07 8b 10 bd 4a a3 de b5 a8 c6 ad 5e d9 e2 23 88 f1 13 65 37 6b e2 6a 73 2e 6a c1 8a 52