ECDH using Python and Hazmat

The most interesting topic area I have found in cybersecurity is the implementation of key exchange with the Diffie-Hellman method. With…

Photo by Takacs Alexandra on Unsplash

ECDH using Python and Hazmat

The most interesting topic area I have found in cybersecurity is the implementation of key exchange with the Diffie-Hellman method. With this, in 1978, Whitfield Diffie and Martin Hellman thought up an amazing approach to create a shared secret between Bob and Alice, and where they can communicate openly: Diffie-Hellman (DH) key exchange. Overall it used discrete logs, and which has since required increasing key sizes to keep up with security standards. A more efficient method these days is to use elliptic curves, and which do not have as great a performance hit when we increase security levels. In this article, we will implement ECDH using the Hazmat privities within the Python cryptography library.

In the Diffie-Hellman (DH) key exchange method, we have a base of g and a shared prime number of p, and where Bob generates a random value of b and Alice generates a random value of a. Bob sends:

B=g^b (mod p)

to Alice, and Alice sends:

A=g^a (mod p)

to Bob. Alice then generates the shared key of:

B^a (mod p)

and Bob generates the same shared key of:

A^b (mod p)

The shared key is actually:

g^{ab} (mod p)

Unfortunately we now need the shared prime number to have over 2,048 bits to be secure, and which has a significant effect in generating these values. With ECDH (Elliptic Curve Diffie Hellman) we can use much smaller values and for the same security as the Diffie-Hellman methods we typically only need 256 bit security. For a key exchange, Bob generates a random value of b and Alice generates a random value of a. Bob then sends Alice the public key point of:

B=b.G

and Alice sends Bob the point key point of:

A=a.G

and where G is the base point on the curve. Bob then computes:

b.A

and Alice computes:

a.B

They should get the same point and which is:

(ab).G

Bob and Alice can then use this shared secret with a HKDF (HMAC Key Derivation Function) to generate the shared key. An outline of the process is (using dA and dB as the private keys for Alice and Bob, respectively):

And here is an outline of these methods:

Code

The following implements EDCH with the Hazmat primitives [here]:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
import binascii
import sys
Bob_private_key = ec.generate_private_key(ec.SECP384R1())
Alice_private_key = ec.generate_private_key(ec.SECP384R1())
size=32 # 256 bit key
if (len(sys.argv)>1):
type=int(sys.argv[1])
if (len(sys.argv)>2):
size=int(sys.argv[2])
if (type==1): 
Bob_private_key = ec.generate_private_key(ec.SECP192R1())
Alice_private_key = ec.generate_private_key(ec.SECP192R1())
elif (type==2):
Bob_private_key = ec.generate_private_key(ec.SECP224R1())
Alice_private_key = ec.generate_private_key(ec.SECP224R1())
elif (type==3):
Bob_private_key = ec.generate_private_key(ec.SECP256K1())
Alice_private_key = ec.generate_private_key(ec.SECP256K1())
elif (type==4):
Bob_private_key = ec.generate_private_key(ec.SECP256R1())
Alice_private_key = ec.generate_private_key(ec.SECP256R1())
elif (type==5):
Bob_private_key = ec.generate_private_key(ec.SECP384R1())
Alice_private_key = ec.generate_private_key(ec.SECP384R1())
elif (type==6):
Bob_private_key = ec.generate_private_key(ec.SECP521R1())
Alice_private_key = ec.generate_private_key(ec.SECP521R1())
elif (type==7):
Bob_private_key = ec.generate_private_key(ec.BrainpoolP256R1())
Alice_private_key = ec.generate_private_key(ec.BrainpoolP256R1())
elif (type==8):
Bob_private_key = ec.generate_private_key(ec.BrainpoolP384R1())
Alice_private_key = ec.generate_private_key(ec.BrainpoolP384R1())
elif (type==9):
Bob_private_key = ec.generate_private_key(ec.BrainpoolP512R1())
Alice_private_key = ec.generate_private_key(ec.BrainpoolP512R1())

Bob_shared_key = Bob_private_key.exchange(ec.ECDH(), Alice_private_key.public_key())
Bob_derived_key = HKDF(algorithm=hashes.SHA256(),length=size,salt=None,info=b'',).derive(Bob_shared_key)
Alice_shared_key = Alice_private_key.exchange(ec.ECDH(), Bob_private_key.public_key())
Alice_derived_key = HKDF(algorithm=hashes.SHA256(),length=size,salt=None,info=b'',).derive(Alice_shared_key)
print ("Name of curve: ",Bob_private_key.public_key().curve.name)
print (f"Generated key size: {size} bytes ({size*8} bits)")
vals = Bob_private_key.private_numbers()
print (f"\nBob private key value: {vals.private_value}")
vals=Bob_private_key.public_key().public_numbers()
enc_point=binascii.b2a_hex(vals.encode_point()).decode()
print("Bob's public key: ",enc_point)
vals = Alice_private_key.private_numbers()
print (f"\nAlice private key value: {vals.private_value}")
vals=Alice_private_key.public_key().public_numbers()
enc_point=binascii.b2a_hex(vals.encode_point()).decode()
print("Alice's public key: ",enc_point)

print ("\nBob's derived key: ",binascii.b2a_hex(Bob_derived_key).decode())
print("Alice's derived key: ",binascii.b2a_hex(Alice_derived_key).decode())

In the sample run, we see, in this case, that Bob and Alice create a 256-bit secret value (their private key) and then generate a 512-bit public key point. The public key points are exchanged, and then the multiply the point they received with their private key value, and the should have the same shared value. Next we feed this shared value into a key derivation function (KDF) such as HKDF, and they will generate the same shared encryption key. This key is often used with a symmetric encryption method, such as for AES 128-bit or AES 256-bit [here]:

Name of curve:  secp256k1
Generated key size: 32 bytes (256 bits)
Bob private key value: 87165408792846072971125111720752819149637739881303496701802332035582951466973
Bob's public key: 048c5cb2fb30e5be4b7bd3bbbae19fad2463ebd329d65bc3f9ab822215988dc195ad7ca4582302913c9621bba3e7afc3ca5631205b5308fc4a426c909b8d503c2e
Alice private key value: 36396609608432111845465861440546983604159401901682691019359757917903012948325
Alice's public key: 04810731a232c6f393a77651964364676742f1e1508ea8ef9eb1aff32b0b1b540333d2a781943f291dfba0f274e55a816df2681bc1e81b8d8a29e59c4bc755f052
Bob's derived key:  321904b2b78d9bc467062ebf4820f5b92497256186b946c53585fb57c5f5df4f
Alice's derived key: 321904b2b78d9bc467062ebf4820f5b92497256186b946c53585fb57c5f5df4f

The running code is here:

Conclusions

From DH to ECDH, the Diffie-Hellman method has provided the core of security on the Internet. But, there’s a problem coming up … quantum computers, and where both discrete log and elliptic curve methods will be easily cracked. So, what’s next? Well, NIST has reached the final round for the winner in the replacement for ECDH, and it is likely to be either CRYSTALS-KYBER, SABER or McEliece: