Encrypt data for a recipient’s public key

Use this when anybody who knows the recipient’s public key must be able to encrypt data, but only the recipient, who has the matching private key, can decrypt it. The two parties don’t need to share a secret in advance.

Alternative: RSA

If the recipient already has an RSA key, or you must interoperate with a system based on RSA, encrypt a random AES key with RSA-OAEP, and the data with the AES key:

from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes

rsa_private_key = RSA.generate(3072)      # the recipient's key
rsa_public_key = rsa_private_key.public_key()

# The sender
session_key = get_random_bytes(16)
enc_session_key = PKCS1_OAEP.new(rsa_public_key).encrypt(session_key)
nonce = get_random_bytes(12)
cipher = AES.new(session_key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(b"Attack at dawn")
data = enc_session_key + nonce + ciphertext + tag

# The recipient
key_len = rsa_private_key.size_in_bytes()          # 384 bytes for RSA-3072
enc_session_key, nonce = data[:key_len], data[key_len:key_len + 12]
ciphertext, tag = data[key_len + 12:-16], data[-16:]
session_key = PKCS1_OAEP.new(rsa_private_key).decrypt(enc_session_key)
cipher = AES.new(session_key, AES.MODE_GCM, nonce=nonce)
assert cipher.decrypt_and_verify(ciphertext, tag) == b"Attack at dawn"

RSA-OAEP alone can only encrypt short data (at most 342 bytes with a 3072-bit key), which is why it is only used for the AES key. Do not use PKCS#1 v1.5 encryption in new designs.