Hazmat RSA Signatures
[Hazmat Home][Home]
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 primitives in the Python cryptograpy 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.
|
Theory
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")
Coding
The following is the code to generate a good signature and a bad one:
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:
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-----
Note that we normally use RSA keys of 2,048 bits and above, as the state-of-the-art in factorizing the modulus is approaching 1,024 bits.