Lab - Asymmetric Cryptography

Slides

Download here

Introduction

In this guide we will develop programs that use cryptographic methods, relying on the Python3 Cryptography module. Write your own programs as you complete the exercises. Environment setup is in the annex. The exercise resources include a requirements.txt and optional starter files.

RSA

RSA key pair generation

Create a small program to generate an RSA key pair, with a key length specified by the user (1024, 2048, 3072 or 4096). The program must save the key pair in two files, one for the private key and the other one for the public key, whose names should also be specified by the user. The keys can be saved as binary blobs but, probably, the PEM format will be more appropriate.

Run the program several times, varying the key length, and register the elapsed time using the time program in the shell (use man time for more information), for example:

time python3 keygen.py ./pub.pem ./priv.pem 4096

Since key generation involves searching for random primes, run each key length a few times and compare the variation between runs.

Questions:

  • What do you think of using 4096-bit keys by default in relation to speed?
  • How does the size of the key files vary with the number of bits?
  • Today, 1024-bit RSA keys are considered insecure. Which key length would you choose for a new system?

RSA Encryption

Create a small program to encrypt a file using the RSA algorithm.

The user must indicate the following data:

  1. Name of the original file to encrypt;
  2. Name of the file with the public key;
  3. Name for the encrypted file.

For the encryption select the PKCS #1 v1.5 padding scheme (padding.PKCS1v15()). Note that this scheme is not recommended for new applications; OAEP should be preferred.

Also, pay attention to the size of the original file, i.e., the file to encrypt. A single RSA operation can only encrypt a limited amount of data. With PKCS #1 v1.5, the maximum plaintext size per operation is the key size (in bytes) minus eleven bytes of padding. For example, using a 1024-bit RSA key (128 bytes), each operation can encrypt at most 117 bytes (128 - 11), and always produces 128 bytes of ciphertext. Larger files must be split into chunks.

Optional: repeat the exercise with OAEP padding (with SHA-256). What is the maximum plaintext size per operation in that case?

RSA Decryption

Create a small program to decrypt the contents of a file using the RSA algorithm. The user must indicate:

  1. Name of the encrypted file;
  2. Name of the file to save the decrypted content;
  3. Name of the file containing the private key to use.

Verify that the decrypted file is identical to the original one (e.g., using cmp or sha256sum).

Large file encryption

As should be known by now, RSA encryption is not efficient and is not recommended to encrypt data bigger than a single block. Let’s conduct a benchmark.

Symmetric vs. Asymmetric cryptography benchmark

Generate two files with 100 KiB and 8 MiB of size. Using these files and the time application, register the time it takes to encrypt and decrypt these files with RSA (key = 1024 bits) and AES128. Employ the applications developed in this guide and in the previous one.

The following bash commands can be used to generate the files:

dd if=/dev/urandom of=file_100k.bin bs=1024 count=100
dd if=/dev/urandom of=file_8M.bin bs=1M count=8

Note: the 1024-bit key is used here only to keep the benchmark short. Compare also encryption and decryption times with RSA: are they similar? Why?

Symmetric and Asymmetric cryptography combination

Consider a large file (500 MB, for example) that you want to send to some person, with the guarantee that only that person can decrypt the file. Moreover, you have the public key of that person, and you are not able to contact the person before sending the file. So, you have a big file to transmit to a person whose public key you know, but the process will be very slow if you encrypt the file using RSA. However, you can use any other encryption algorithm to encrypt the file, but remember that only the receiver must be able to decrypt the file.

Questions:

  • What combination of encryption technologies allows you to efficiently send the file to the recipient, with the guarantee that only that person can decrypt the file?

A typical solution is called Hybrid Encryption, which combines two ciphers: a symmetric one to encrypt the file with a random key, and an asymmetric one to encrypt the key used in the previous step.

Implement a program that is able to encrypt/decrypt a large file using the AES128 algorithm, employing a randomly generated secret key (and a random IV, e.g., CBC mode with PKCS#7 padding, as in the previous guide). The secret key should be supplied to the intended receiver ciphered with RSA using the receiver’s public key. Use RSA-OAEP with SHA-256 to encrypt the symmetric key. Process large files incrementally rather than loading the whole file into memory. The receiver should be able to reverse the process and obtain the original version of the large file. The program should accept the following arguments:

  1. Operation to perform (encrypt/decrypt);
  2. Name of the (large) file to encrypt/decrypt;
  3. Name of the file with the key to be used (public/private).

Questions:

  • With this method, what is sent to the destination?
  • Should we always send the public key?
  • Which values must be kept secret, and which can be sent in the clear?

Elliptic curves

Elliptic Curve Cryptography (ECC) is a lot different from RSA.

First, whilst in RSA one chooses the length of the RSA modulus (in bits), in ECC one chooses a curve, and the curve defines the key length.

Second, the private and public keys that belong to the same key pair are of a different nature: the private key is an integer (a scalar), whilst the public key is a two-coordinate point. The public component is a point given by the multiplication of the private key with a so-called curve generator (G), which is a point of the curve. Since the sum of two points defines a new point (elliptic curves have a special algebra…), and the multiplication of G by the private key (a scalar) is nothing more than a sum of G values, it is simple to conclude that the public key is a point.

Third, there is no such thing as an encryption with a public key and a posterior decryption with the corresponding private key. This is not used at all. Instead, a method similar to a Diffie-Hellman key agreement (ECDH) is used. For encrypting a message to a receiver B, the sender A uses B’s public key and a new (ephemeral) private key to compute a point in the curve, and from that point derives a secret, symmetric key. The message is then encrypted with that symmetric key and sent to B, together with the public component of the ephemeral private key created by A. The receiver B multiplies the received public key with its own private key and reaches the same point in the curve, which it can use to derive the exact same secret, symmetric key and use it to decipher the message. This is the basic idea behind ECIES (Elliptic Curve Integrated Encryption Scheme), which, in its standard form, also adds a MAC to protect the integrity of the ciphertext.

There are many elliptic curves, both proposed in standards and other publications. NIST originally proposed 15 curves, known as P, B and K curves (5 of each). The name stems from the mathematical elements used in the curves – P from prime, B from binary, K from Koblitz, a variant of B curves. The most recent NIST recommendations (SP 800-186) deprecate the B and K curves. Other curves are recommended by de facto standards, such as RFC 7748, which describes Curve25519 and Curve448. The SafeCurves publication provides some guidelines for selecting a good curve.

ECC key pair generation

ECC key pair generation is fairly simple. First, one chooses a curve; we will use P-256 (also known as secp256r1).

Then, we generate a random private key for that curve. This is a random integer smaller than the order of the curve (about 256 bits). Then, from that private key, we generate the public key.

Create a program that generates a key pair and saves it in a file. The private component should be protected by a password, read without echoing it (e.g., with getpass). You can use the PEM format for encapsulating each of the keys. We will assume this program will be called p256_gen.py.

python3 p256_gen.py > KPair

Note: you can generate two PEM contents, one for the private component, another for the public component, and store them in the same PEM file. We will consider this approach below.

Secure messaging with public encryption and private decryption

Create a program ECC_encrypt.py that encrypts the contents received from the standard input to the standard output. The program should have as a parameter a file containing the public key of the receiver (only the public component of KPair is needed).

The contents produced must contain both the encrypted input and the originator’s ephemeral public key. You can use any message format for that, for instance, JSON (do not forget to use Base64 for encoding binary contents into strings for JSON). The shared secret produced by ECDH should not be used directly as a key: derive the symmetric key from it with a key derivation function (e.g., HKDF with SHA-256). For symmetric encryption, you can use AES in CTR mode and an IV derived from the secret key (e.g., by hashing it). This is acceptable here only because a new ephemeral key pair, and therefore a new symmetric key, is used for every message.

python3 ECC_encrypt.py KPair < plaintext > ciphertext

Create a program ECC_decrypt.py that decrypts the contents received from the standard input to the standard output. The program should have as parameters a file containing the private key of the receiver and request the password without echoing it to access that private key. The format of the input is the one produced by ECC_encrypt.

python3 ECC_decrypt.py KPair < ciphertext > recovered_plaintext

After these two commands, the contents of plaintext should be equal to the contents of recovered_plaintext:

cmp plaintext recovered_plaintext && echo OK

Questions:

  • Encrypt the same plaintext twice. Are the two ciphertext files equal? Why?
  • What would happen if the sender reused the same ephemeral private key for every message?

Further Reading

  1. Python3 Cryptography: RSA
  2. Python3 Cryptography: Elliptic curve cryptography
  3. Python3 Cryptography: Key derivation functions (HKDF)
  4. SafeCurves

Python helper to get started (only if strictly necessary): find asymmetric_cryptography_python.zip in the resources’ helpers/ directory. It provides a scaffold; complete and explain the missing logic yourself.

C or Rust alternative: this lab can also be implemented in C or Rust. Find starter files in asymmetric_cryptography_c_rust.zip, in the helpers/ directory of the exercise resources. Use them as a starting point and fill in the code required for each exercise.

Annex: Python environment setup

Extract the exercise resources in the course VM and create an isolated Python environment:

unzip lab5-resources.zip -d lab5
cd lab5
python3 -m venv .venv
source .venv/bin/activate

The supplied requirements.txt contains:

cryptography==46.0.5

Install the dependencies with pip:

python -m pip install -r requirements.txt

Protect files containing private keys, including a combined KPair file:

chmod 600 KPair

Share only the public component with other participants. Use a separate working directory and fresh output names for each experiment.

Authors: João Paulo Barraca, Vitor Cunha, Paulo C. Bartolomeu, Alfredo Matos, Catarina Silva

Previous
Next