RSA Signatures, Python and Hazmat

At the core of digital trust is the usage of digital signatures. With this, we can verify the creator of the data, and also that it has not…

Photo by Cytonn Photography on Unsplash

RSA Signatures, Python and Hazmat

At the core of digital trust is the usage of digital signatures. With this, we can verify the creator of the data, and also that it has not been modified. We do this using public-key encryption, and in this article, we will look at how we can use the hazmat (Hazardous Material) primitives in the Python cryptography library.

With public-key encryption, we create a key pair: a public key and a private key. If Alice is sending data to Bob, she can add her digital signature, and which will prove that she is the sender and also verify that the data has not been changed. She does this by signing the data with her private key, and then Bob can prove the signature with Alice’s public key. In this example, we will use RSA keys to sign a message, and then verify the correct signature, but verify that an incorrect signature will fail.

To generate a key we can simply add:

private_key = rsa.generate_private_key(public_exponent=65537,key_size=size)

and where size is the number of bits in the public modulus (N), and the public_exponent is typically the value of 65,537. This will then generate a private key and a public key. To extract the public key we can use:

pub = private_key.public_key()

To create a signature from a message we use our private key to sign it, aloong with a salt value and a given hash type (SHA-256):

signature = private_key.sign(message,padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),hashes.SHA256())

To verify we use the associated public key, and generate an exception if it is incorrect:

try:
rtn=pub.verify(signature,message,padding.PSS(mgf=padding.MGF1(hashes.SHA256()),salt_length=padding.PSS.MAX_LENGTH),hashes.SHA256())
except exceptions.InvalidSignature:
print("A bad signature failed")
else:
print("Good signature verified")

The following is the code and which will generate a good signature and a bad one [here]:

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import exceptions
import sys
import binascii
size=512
message = b"Hello world"
if (len(sys.argv)>1):
message=str(sys.argv[1]).encode()
if (len(sys.argv)>2):
size=int(sys.argv[2])
print("Message: ",message)
print("Key size: ",size)
try:
private_key = rsa.generate_private_key(public_exponent=65537,key_size=size)

  pub = private_key.public_key()
  signature = private_key.sign(message,padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),hashes.SHA256())
  print ("\nSignature: ",binascii.b2a_hex(signature).decode())
  try:
rtn=pub.verify(signature,message,padding.PSS(mgf=padding.MGF1(hashes.SHA256()),salt_length=padding.PSS.MAX_LENGTH),hashes.SHA256())
except exceptions.InvalidSignature:
print("A bad signature failed")
else:
print("Good signature verified")

  try:
pub.verify(signature,b"test",padding.PSS(mgf=padding.MGF1(hashes.SHA256()),salt_length=padding.PSS.MAX_LENGTH),hashes.SHA256())
except exceptions.InvalidSignature:
print("A bad signature failed")
else:
print("Bad signature verified")
  print ("\nVerified: ",rtn)
print("\n=== Private Key PEM format ===")
pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.PKCS8,encryption_algorithm=serialization.NoEncryption())

  print ("\nPrivate key (PEM):\n",pem.decode())

  pem = pub.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo).decode("utf-8")
  print("\n=== Public Key format ===")
print ("Public key (PEM):\n",pem)

except Exception as e:
print(e)

A sample run for a key size of 512 bits is [here]:

Message:  Testing 1 .. 2 ... 3
Key size: 512
Signature:  797e1eefdd62900214f9a9c18735f2a965d7532cb65d212c55f410f792ad228a0d2a201e6531e10c1f3b5e4969f8a37ce12db09e86df3189ac3d3157c22a8e14
Good signature verified
A bad signature failed
Verified:  None
=== Private Key PEM format ===
Private key (PEM):
-----BEGIN PRIVATE KEY-----
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAx6ut7BqEj2AwSB7w
eNCG7F2XNtkTLbRtHSQOYRblEL5Jw3po93TOvtDD+Rv7vCQAJD+vMdv7dxAXwN4X
7aUDawIDAQABAkEAslWt42DT4NLCjMfFc8Kbn2T/9+bt8DZj9lEL3r96G/ajk7q9
fFn7N7pLlW833lmVXdPxASyUbYlHhcEdXeN1UQIhAPetnCVYFWG2J5H14HJOgd4F
fOm412CjUR7FciYwJHwvAiEAzmEinfdRZcQscqnMl54sUgoB0QXP934vAA3QXUNs
sYUCIFYyPV1hwk83LZ5Gi848NEYocsiEY7BmJh0nagqQRqJRAiEAxtMp6Im2L5a5
Q5Z2drN5+2eMrHUvED7OxPyJ+u/ULYkCIQDIXCKb85s8JbjpjhtQ0cXdqTuCW0C+
UhMxnJuCYA5jQA==
-----END PRIVATE KEY-----

=== Public Key format ===
Public key (PEM):
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMerrewahI9gMEge8HjQhuxdlzbZEy20
bR0kDmEW5RC+ScN6aPd0zr7Qw/kb+7wkACQ/rzHb+3cQF8DeF+2lA2sCAwEAAQ==
-----END PUBLIC KEY-----

And here is the running code:

Conclusions

At the core of digital trust is the digital signature. Unfortunately, we still live in a world with wet signatures and where we still paste a graphic of our wet-signature into documents. Our world needs to move to provide trust in every transaction, and digital signatures are the way to achieve this.