Elliptic Curve Keys, Pythonand Hazmat

Well, RSA has been around for over 40 years, and it is struggling in places. In terms of key sizes, we are now at 2,048-bit keys and above…

Photo by Scott Webb on Unsplash

Elliptic Curve Keys, Python and Hazmat

Well, RSA has been around for over 40 years, and it is struggling in places. In terms of key sizes, we are now at 2,048-bit keys and above. For a lightweight device, we will struggle to perform these sizes of operations. And so Elliptic Curve Cryptography (ECC) has come to our rescue, and where we use typical key sizes of just 256 bits. In fact, Bitcoin and Ethereum, and most blockchain methods use ECC for their keys. So, let’s dive in and implement some ECC key generation, and use the hazmat primitives with the cryptography library.

The generation of the key pair is fairly simple, and where we just define a curve type:

private_key = ec.generate_private_key(ec.SECP256K1())

This value will contain the private key (n) and which (in this case) is a 256-bit random number. This key must be kept secret. If we want to view the value we simply use:

vals = private_key.private_numbers()
no_bits=vals.private_value.bit_length()
print (f"Private key value: {vals.private_value}. Number of bits {no_bits}")

An example ouput confirms that the private key has around 256 bits:

Private key value: 51959644559546985352323146960120412494517429222374217317044884172801854956286. Number of bits 255

The private key will also contain the public key, and we can access this with:

public_key = private_key.public_key()

The public key is generated by taking the base point on the curve (G) and computing nG (and which is the point G added n times). Anyone can receive the public key point. Overall we often generate a compressed public key point, and which starts with a ‘0x40’ and then followed by the x-coordinate and then the y-coordinate. The following converts the public key point to a hex string and then parses it for the x-coordinate and the y-coordinate:

public_key = private_key.public_key()
vals=public_key.public_numbers()
enc_point=binascii.b2a_hex(vals.encode_point()).decode()
print (f"\nPublic key encoded point: {enc_point} \nx={enc_point[2:(len(enc_point)-2)//2+2]} \ny={enc_point[(len(enc_point)-2)//2+2:]}")

A sample runs shows the compressed point, and the x- and y-coordinates (I have added in spaces to show the values):

Public key encoded point: 04 5edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda 11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6 
x=5edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda
y=11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6

There are two main ways of showing the keys: PEM and DER. In PEM we have a text format with a header and a footer:

-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEXt6opLszIEbjxYOVb6GBfjxqdRa/dcR+
PqFRn8T969oR7X88igF3ifNLv9NpYxg+OGEsq2wrgmC2DOsXTmrz1g==
-----END PUBLIC KEY-----

Whereas DER is just the pure binary values converted to hex:

Private key (DER):
b'308184020100301006072a8648ce3d020106052b8104000a046d306b0201010420649953e6a5fd485db869bf1cd6382fee2f34bc63bf1b46cbab458a07dddb2480a144034200045edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6'

If we use this reader [here], we get the DER interpreted format of:

[U] SEQUENCE (30)
[U] INTEGER (02): 0
[U] SEQUENCE (30)
[U] OBJECT (06): 1.2.840.10045.2.1 - ECC
[U] OBJECT (06): 1.3.132.0.10 - secp256k1
[U] OCTET STRING: 0xb'306B0201010420649953E6A5FD485DB869BF1CD6382FEE2F34BC63BF1B46CBAB458A07DDDB2480A144034200045EDEA8A4BB332046E3C583956FA1817E3C6A7516BF75C47E3EA1519FC4FDEBDA11ED7F3C8A017789F34BBFD36963183E38612CAB6C2B8260B60CEB174E6AF3D6'

and where we can see we are using the secp256k1 curve (as used in Bitcoin). If you want to learn more about DER format, try here:

The following is the code [here]:

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
import binascii
import sys
private_key = ec.generate_private_key(ec.SECP384R1())
if (len(sys.argv)>1):
type=int(sys.argv[1])
if (type==1): private_key = ec.generate_private_key(ec.SECP192R1())
elif (type==2): private_key = ec.generate_private_key(ec.SECP224R1())
elif (type==3): private_key = ec.generate_private_key(ec.SECP256K1())
elif (type==4): private_key = ec.generate_private_key(ec.SECP256R1())
elif (type==5): private_key = ec.generate_private_key(ec.SECP384R1())
elif (type==6): private_key = ec.generate_private_key(ec.SECP521R1())
elif (type==7): private_key = ec.generate_private_key(ec.BrainpoolP256R1())
elif (type==8): private_key = ec.generate_private_key(ec.BrainpoolP384R1())
elif (type==9): private_key = ec.generate_private_key(ec.BrainpoolP512R1())
vals = private_key.private_numbers()
no_bits=vals.private_value.bit_length()
print (f"Private key value: {vals.private_value}. Number of bits {no_bits}")
public_key = private_key.public_key()
vals=public_key.public_numbers()
enc_point=binascii.b2a_hex(vals.encode_point()).decode()
print (f"\nPublic key encoded point: {enc_point} \nx={enc_point[2:(len(enc_point)-2)//2+2]} \ny={enc_point[(len(enc_point)-2)//2+2:]}")

pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8,encryption_algorithm=serialization.NoEncryption())
der = private_key.private_bytes(encoding=serialization.Encoding.DER,format=serialization.PrivateFormat.PKCS8,encryption_algorithm=serialization.NoEncryption())
print ("\nPrivate key (PEM):\n",pem.decode())
print ("Private key (DER):\n",binascii.b2a_hex(der))
pem = public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo)
der = public_key.public_bytes(encoding=serialization.Encoding.DER,format=serialization.PublicFormat.SubjectPublicKeyInfo)
print ("\nPublic key (PEM):\n",pem.decode())
print ("Public key (DER):\n",binascii.b2a_hex(der))

A sample run is [here]:

Private key value: 45502191522452971277594115898391536986083369375507907918033060970171752719488. Number of bits 255
Public key encoded point: 045edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6 
x=5edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda
y=11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6
Private key (PEM):
-----BEGIN PRIVATE KEY-----
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgZJlT5qX9SF24ab8c1jgv
7i80vGO/G0bLq0WKB93bJIChRANCAARe3qikuzMgRuPFg5VvoYF+PGp1Fr91xH4+
oVGfxP3r2hHtfzyKAXeJ80u/02ljGD44YSyrbCuCYLYM6xdOavPW
-----END PRIVATE KEY-----
Private key (DER):
b'308184020100301006072a8648ce3d020106052b8104000a046d306b0201010420649953e6a5fd485db869bf1cd6382fee2f34bc63bf1b46cbab458a07dddb2480a144034200045edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6'
Public key (PEM):
-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEXt6opLszIEbjxYOVb6GBfjxqdRa/dcR+
PqFRn8T969oR7X88igF3ifNLv9NpYxg+OGEsq2wrgmC2DOsXTmrz1g==
-----END PUBLIC KEY-----
Public key (DER):
b'3056301006072a8648ce3d020106052b8104000a034200045edea8a4bb332046e3c583956fa1817e3c6a7516bf75c47e3ea1519fc4fdebda11ed7f3c8a017789f34bbfd36963183e38612cab6c2b8260b60ceb174e6af3d6'

Conclusions

The core of trust on the Internet is now typically ECC. It is used for digital signatures, cryptocurrency, IoT security, and so much more. In fact, the key exchange to secure this web page is likely to have used ECC in its operation.