5.8 RSA RETURNS!

EXERCISE 5.8: RSA RETURNS

Create an encryption and authentication system for Alice, Bob, and EATSA. This system needs to be able to generate key pairs and save them to disk under different operator names. To send a message, it needs to load a private key of the operator and a public key of the recipient. The message to be sent is then signed by the operator’s private key. Then the concatenation of the sender’s name, the message, and the signature is encrypted.

To receive a message, the system loads the private key of the operator and decrypts the data extracting the sender’s name, the message, and the signature. The sender’s public key is loaded to verify the signature over the message.


You can reuse the rsa_encrypt(m: bytes, public_key: rsa.RSAPublicKey) and rsa_decrypt(c: bytes, private_key: rsa.RSAPrivateKey) functions from exercise 4.11.

You can use the following functions for creating and verifying the signatures:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes


def create_rsa_signature(m: bytes, private_key: rsa.RSAPrivateKey) -> bytes:
    return private_key.sign(
        data=m,
        padding=padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH,
        ),
        algorithm=hashes.SHA256(),
    )


def verify_rsa_signature(m: bytes, public_key: rsa.RSAPublicKey, sig: bytes) -> None:
    '''Throws InvalidSignature, if sig is not valid.'''
    public_key.verify(
        signature=sig,
        data=m,
        padding=padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH,
        ),
        algorithm=hashes.SHA256(),
    )

You can use the following functions to read/write the keys from the hard disk:

from cryptography.hazmat.primitives import serialization
import os.path


def read_public_key_from_file(fname: str) -> rsa.RSAPublicKey:
    if not os.path.exists(fname):
        raise FileNotFoundError()

    with open(fname, 'rb') as f:
        public_key = serialization.load_pem_public_key(
            data=f.read(),
            backend=default_backend(),
        )
    return public_key


def read_private_key_from_file(fname: str) -> rsa.RSAPrivateKey:
    if not os.path.exists(fname):
        raise FileNotFoundError()

    with open(fname, 'rb') as f:
        private_key = serialization.load_pem_private_key(
            data=f.read(),
            backend=default_backend(),
            password=None,
        )
    return private_key


def write_public_key_to_file(fname: str, public_key: rsa.RSAPublicKey) -> None:
    with open(fname, 'wb') as f:
        f.write(
            public_key.public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
            )
        )


def write_private_key_to_file(fname: str, private_key: rsa.RSAPrivateKey) -> None:
    with open(fname, 'wb') as f:
        f.write(
            private_key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.TraditionalOpenSSL,
                encryption_algorithm=serialization.NoEncryption(),
            )
        )