Search code examples
pythoncryptographyelliptic-curvediffie-hellman

How to generate a DH shared key if the peer public key is not encoded as the host private key?


I'm trying to perform a DH key exchange between a host (python) and a device (using C). I'm currently using cryptography.io's elliptic curve exchange algorithm on the host.

This is the situation: I have received the peer's public key as a list of 64 integers, raw as it is, no padding no wrapping no encyption.

However, when I have to generate the shared key, the example shows this:

shared_key = private_key.exchange(ec.ECDH(), peer_public_key)

But throws an error, ofc:

AttributeError 'list' object has no attribute 'curve'

It should be because my private key is a _EllipticCurvePrivateKey object, while the peer public key is just a list.

How do I turn my peer public key to a suitable public key to generate the shared secret?


Solution

  • Cryptography supports the import of raw EC keys with the methods ec.EllipticCurvePublicKey.from_encoded_point() (since version 2.5) for the public key and ec.derive_private_key() (since version 1.6) for the private key.

    Example (for elliptic curve secp256k1):

    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.asymmetric import ec
    
    curve = ec.SECP256K1()
    
    # Key A: Generate a new key pair
    private_keyA = ec.generate_private_key(curve)
    public_keyA = private_keyA.public_key()
    
    #Key B: Derive key pair from raw data
    private_valueB = 0x36A4B92DCD0077CDA3173D218733931497582B733285B8961CAD616E927691B9
    public_valueB = bytes.fromhex('04D4465B9B77F9811BF89B20064A5214C00210D6183910BDC220BA65F8B9D327933C1D7FD3B7DB0A69420A614C9D9303D09BA96C83203927B149A6E28D01E0C253')
    private_keyB = ec.derive_private_key(private_valueB, curve, default_backend())
    public_keyB = ec.EllipticCurvePublicKey.from_encoded_point(curve, public_valueB)
    
    # Shared Keys
    shared_keyA = private_keyA.exchange(ec.ECDH(), public_keyB)
    print(shared_keyA.hex())
    
    shared_keyB = private_keyB.exchange(ec.ECDH(), public_keyA)
    print(shared_keyB.hex())
    

    In the posted code, key pair A is newly generated, while key pair B is generated from raw data. When the code is executed, these keys generate the same secret on both sides as expected.

    Please note regarding the public key, that the point representing the public key can be passed to from_encoded_point() in compressed (only x coordinate) or uncompressed format (both, x and y coordinates), see e.g. here, with a starting byte indicating whether the key is in compressed or uncompressed format.
    For example, if the point is to be passed in uncompressed format, the starting byte is 0x04, followed by the bytes of the x coordinate and then the bytes of the y coordinate (giving a total length of 65 bytes for the above example).
    If the data is not available in the required format, it must be converted accordingly (manually or programmatically) before it is passed to from_encoded_point().