ECDSA, Python and Hazmat

The other day, I filled in a five page paper form to change my GP, and showed a utility bill with my new address. Everything that I have to…

Photo by Romain Dancre on Unsplash

ECDSA, Python and Hazmat

The other day, I filled in a five page paper form to change my GP, and showed a utility bill with my new address. Everything that I have to fill in my email address using a pen-and-paper just feels like it’s based on a 20th Century world. What we need is to move towards infrastructures that integrate digital identity and digital signing. But what is at the core of digital signing?

ECDSA

At the core of digital signing is normally a method known as ECDSA (Elliptic Curve Digital Signature Algorithm), as it is more efficent and produces smaller encryption keys than its RSA equivalant. With this Alice has a public key and a private key. The owernship of the private key defines her identity as no-one else can have the key. She thus signs a message with her private key, and then Bob proves the signature with her public key. Under the hood there is r and an s value, and which are used to verify the signature. The mechanics of the (r,s) checking is defined here:

In this article, we will just use the sign() and verify() methods to sign a message and verify it. To generate a key pair we simply use the following and with the curve type defined (in this case it is Secp384r1):

private_key = ec.generate_private_key(ec.SECP384R1())

We now need to extract the public key, so that someone can check the signature:

public_key = private_key.public_key()

With a message, we will generate a hash value with a given hash type and then create the signature. We sign using the private key, the message, and the sign() method. We can then use the verify() method to prove the signature. An exception on the verification will identity an incorrect signature:

print ("Message: ",data.decode())
try:
signature = private_key.sign(data,ec.ECDSA(hashes.SHA256()))
print ("Good Signature: ",binascii.b2a_hex(signature).decode())
public_key.verify(signature, data, ec.ECDSA(hashes.SHA256()))
except exceptions.InvalidSignature:
print("A bad signature failed")
else:
print("Good signature verified")

A run with a good signature is:

Name of curve:  secp192r1
Message: My test data
Good Signature: 3034021804eb81c512579eafccfc4f54d97cbaac45d6b00c426f0d02021862efa33022bc532b651fda863ce4ca52409154a0432cf267
Good signature verified

The signature is in a DER format with an r and an s value, and can be read as [here]:

DER: 3034021804eb81c512579eafccfc4f54d97cbaac45d6b00c426f0d02021862efa33022bc532b651fda863ce4ca52409154a0432cf267
[U] SEQUENCE (30)
[U] INTEGER (02): 120636795522494221738083360226736081266793749028489071874
[U] INTEGER (02): 2425905716110104551919552660080279451279883668482964648551

Coding and sample run

The following is the code [here]:

from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography import exceptions
import binascii
import sys
private_key = ec.generate_private_key(ec.SECP384R1())
data = b"test"
if (len(sys.argv)>2):
type=int(sys.argv[2])
if (len(sys.argv)>1):
data=str(sys.argv[1]).encode()

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())
elif (type==10): private_key = ec.generate_private_key(ec.SECT163K1())
elif (type==11): private_key = ec.generate_private_key(ec.SECT163R2())
elif (type==12): private_key = ec.generate_private_key(ec.SECT233K1())
elif (type==13): private_key = ec.generate_private_key(ec.SECT233R1())
elif (type==14): private_key = ec.generate_private_key(ec.SECT283K1())
elif (type==15): private_key = ec.generate_private_key(ec.SECT233R1())
private_vals = private_key.private_numbers()
no_bits=private_vals.private_value.bit_length()
print (f"Private key value: {private_vals.private_value}. Number of bits {no_bits}")
public_key = private_key.public_key()
pub=public_key.public_numbers()
print ("Name of curve: ",pub.curve.name)
print ("Message: ",data.decode())
try:
  signature = private_key.sign(data,ec.ECDSA(hashes.SHA256()))
print ("Good Signature: ",binascii.b2a_hex(signature).decode())
public_key.verify(signature, data, ec.ECDSA(hashes.SHA256()))
except exceptions.InvalidSignature:
print("A bad signature failed")
else:
print("Good signature verified")
try:
  signature = private_key.sign(b"bad message",ec.ECDSA(hashes.SHA256()))
print ("Bad Signature: ",binascii.b2a_hex(signature).decode())
public_key.verify(signature, data, ec.ECDSA(hashes.SHA256()))
except exceptions.InvalidSignature:
print("A bad signature failed")
else:
print("Good signature verified")
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))

The following is a sample run [here]:

Private key value: 2764182454245540237772308167620229600480588897947498851651. Number of bits 191
Name of curve: secp192r1
Message: My test data
Good Signature: 3034021804eb81c512579eafccfc4f54d97cbaac45d6b00c426f0d02021862efa33022bc532b651fda863ce4ca52409154a0432cf267
Good signature verified
Bad Signature: 30350218660f6b27c13feee5524f4e0a643466f45ecbf513c5e6660c021900f3ed00930639457fb58093e954d316082dc167e64f475b51
A bad signature failed
Private key (PEM):
-----BEGIN PRIVATE KEY-----
MG8CAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEVTBTAgEBBBhwu2lQ4vwkji2+qiQ6
vMot3eF41nEgLUOhNAMyAASXqyOm1I0u6zLvf+wXuonwcLBlj7CQSorIA/WNCZfc
3dfRhZ/6HYETkVeZy2lgI/Y=
-----END PRIVATE KEY-----
Private key (DER):
b'306f020100301306072a8648ce3d020106082a8648ce3d03010104553053020101041870bb6950e2fc248e2dbeaa243abcca2ddde178d671202d43a1340332000497ab23a6d48d2eeb32ef7fec17ba89f070b0658fb0904a8ac803f58d0997dcddd7d1859ffa1d8113915799cb696023f6'
Public key (PEM):
-----BEGIN PUBLIC KEY-----
MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEl6sjptSNLusy73/sF7qJ8HCwZY+w
kEqKyAP1jQmX3N3X0YWf+h2BE5FXmctpYCP2
-----END PUBLIC KEY-----
Public key (DER):
b'3049301306072a8648ce3d020106082a8648ce3d0301010332000497ab23a6d48d2eeb32ef7fec17ba89f070b0658fb0904a8ac803f58d0997dcddd7d1859ffa1d8113915799cb696023f6'

And here is the running code:

Conclusions

And there you go. A few lines of Python and you have a key pair and a digital signature.