Cryptographic recipes and primitives for Python developers
—
Authenticated Encryption with Associated Data (AEAD) providing encryption and authentication in a single operation. AEAD ciphers ensure both confidentiality and authenticity of data while allowing additional authenticated data that remains unencrypted.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305, AESCCM, AESSIV, AESOCB3, AESGCMSIVAES in Galois/Counter Mode providing authenticated encryption with excellent performance.
class AESGCM:
def __init__(self, key: bytes):
"""
Initialize AES-GCM with key.
Args:
key (bytes): 128, 192, or 256-bit key (16, 24, or 32 bytes)
"""
@classmethod
def generate_key(cls, bit_length: int) -> bytes:
"""
Generate random key for AES-GCM.
Args:
bit_length (int): Key length in bits (128, 192, or 256)
Returns:
bytes: Random key of specified length
"""
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Encrypt data with authentication.
Args:
nonce (bytes): 96-bit (12 bytes) nonce. Must be unique per key.
data (bytes): Data to encrypt
associated_data (bytes, optional): Additional data to authenticate but not encrypt
Returns:
bytes: Encrypted data with authentication tag appended
Note:
Never reuse nonce with same key - this breaks security completely
"""
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Decrypt and verify authenticated data.
Args:
nonce (bytes): Same nonce used for encryption
data (bytes): Encrypted data with authentication tag
associated_data (bytes, optional): Same associated data from encryption
Returns:
bytes: Decrypted plaintext data
Raises:
InvalidTag: If authentication verification fails
"""Modern stream cipher with Poly1305 MAC for authenticated encryption.
class ChaCha20Poly1305:
def __init__(self, key: bytes):
"""
Initialize ChaCha20-Poly1305 with key.
Args:
key (bytes): 256-bit key (32 bytes)
"""
@classmethod
def generate_key(cls) -> bytes:
"""
Generate random 256-bit key.
Returns:
bytes: 32-byte random key
"""
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Encrypt data with ChaCha20-Poly1305.
Args:
nonce (bytes): 96-bit (12 bytes) nonce. Must be unique per key.
data (bytes): Data to encrypt
associated_data (bytes, optional): Additional authenticated data
Returns:
bytes: Encrypted data with Poly1305 tag appended
"""
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Decrypt and verify ChaCha20-Poly1305 data.
Args:
nonce (bytes): Same nonce used for encryption
data (bytes): Encrypted data with authentication tag
associated_data (bytes, optional): Same associated data from encryption
Returns:
bytes: Decrypted plaintext
Raises:
InvalidTag: If Poly1305 authentication fails
"""AES Counter mode with CBC-MAC for authenticated encryption.
class AESCCM:
def __init__(self, key: bytes, tag_length: int = 16):
"""
Initialize AES-CCM with key and tag length.
Args:
key (bytes): 128, 192, or 256-bit AES key
tag_length (int): Authentication tag length (4, 6, 8, 10, 12, 14, 16 bytes)
"""
@classmethod
def generate_key(cls, bit_length: int) -> bytes:
"""Generate random AES key"""
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Encrypt with AES-CCM.
Args:
nonce (bytes): 7-15 byte nonce (longer nonce = shorter counter)
data (bytes): Data to encrypt
associated_data (bytes, optional): Additional authenticated data
Returns:
bytes: Encrypted data with CBC-MAC tag
"""
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Decrypt and verify AES-CCM data.
Raises:
InvalidTag: If CBC-MAC verification fails
"""Misuse-resistant authenticated encryption that's secure even with nonce reuse.
class AESSIV:
def __init__(self, key: bytes):
"""
Initialize AES-SIV.
Args:
key (bytes): 256, 384, or 512-bit key (32, 48, or 64 bytes)
Key length determines AES variant (AES-128, AES-192, AES-256)
"""
@classmethod
def generate_key(cls, bit_length: int) -> bytes:
"""Generate random key for AES-SIV"""
def encrypt(self, data: bytes, associated_data: List[bytes] = None) -> bytes:
"""
Encrypt with AES-SIV (no nonce required).
Args:
data (bytes): Data to encrypt
associated_data (List[bytes], optional): List of associated data items
Returns:
bytes: SIV (16 bytes) + encrypted data
Note:
AES-SIV is nonce-misuse resistant - safe even with repeated inputs
"""
def decrypt(self, data: bytes, associated_data: List[bytes] = None) -> bytes:
"""
Decrypt AES-SIV data.
Args:
data (bytes): SIV + encrypted data from encrypt()
associated_data (List[bytes], optional): Same associated data from encryption
Returns:
bytes: Decrypted plaintext
Raises:
InvalidTag: If SIV verification fails
"""High-performance authenticated encryption mode.
class AESOCB3:
def __init__(self, key: bytes):
"""
Initialize AES-OCB3.
Args:
key (bytes): 128, 192, or 256-bit AES key
"""
@classmethod
def generate_key(cls, bit_length: int) -> bytes:
"""Generate random AES key"""
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Encrypt with AES-OCB3.
Args:
nonce (bytes): 1-15 byte nonce, must be unique per key
data (bytes): Data to encrypt
associated_data (bytes, optional): Additional authenticated data
Returns:
bytes: Encrypted data with authentication tag
"""
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Decrypt AES-OCB3 data.
Raises:
InvalidTag: If authentication verification fails
"""Misuse-resistant variant of AES-GCM that's secure with nonce reuse.
class AESGCMSIV:
def __init__(self, key: bytes):
"""
Initialize AES-GCM-SIV.
Args:
key (bytes): 128 or 256-bit key (16 or 32 bytes)
"""
@classmethod
def generate_key(cls, bit_length: int) -> bytes:
"""Generate random key for AES-GCM-SIV"""
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Encrypt with AES-GCM-SIV.
Args:
nonce (bytes): 96-bit (12 bytes) nonce
data (bytes): Data to encrypt
associated_data (bytes, optional): Additional authenticated data
Returns:
bytes: Encrypted data with authentication tag
Note:
Secure even with nonce reuse, but unique nonces still recommended
"""
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
"""
Decrypt AES-GCM-SIV data.
Raises:
InvalidTag: If authentication verification fails
"""from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.exceptions import InvalidTag
import os
# Generate key and nonce
key = AESGCM.generate_key(256) # 256-bit key
aesgcm = AESGCM(key)
# Encrypt data
nonce = os.urandom(12) # 96-bit nonce
plaintext = b"Secret message for authenticated encryption"
associated_data = b"public metadata"
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
print(f"Encrypted: {ciphertext.hex()}")
# Decrypt data
try:
decrypted = aesgcm.decrypt(nonce, ciphertext, associated_data)
print(f"Decrypted: {decrypted}")
except InvalidTag:
print("Authentication failed - data may be tampered")from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
import os
# Initialize ChaCha20-Poly1305
key = ChaCha20Poly1305.generate_key()
chacha = ChaCha20Poly1305(key)
# Encrypt with associated data
nonce = os.urandom(12)
message = b"High-speed encrypted message"
metadata = b"timestamp:2024-01-01,user:alice"
encrypted = chacha.encrypt(nonce, message, metadata)
# Decrypt and verify
try:
decrypted = chacha.decrypt(nonce, encrypted, metadata)
print(f"Message: {decrypted}")
print("Authentication successful")
except InvalidTag:
print("Message authentication failed")from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
import json
class SecureFileManager:
def __init__(self, key=None):
self.key = key or AESGCM.generate_key(256)
self.aesgcm = AESGCM(self.key)
def encrypt_file(self, input_path, output_path, metadata=None):
"""Encrypt file with optional metadata"""
with open(input_path, 'rb') as f:
plaintext = f.read()
nonce = os.urandom(12)
associated_data = json.dumps(metadata or {}).encode() if metadata else None
ciphertext = self.aesgcm.encrypt(nonce, plaintext, associated_data)
# Store nonce + ciphertext + metadata length + metadata
with open(output_path, 'wb') as f:
f.write(nonce) # First 12 bytes
f.write(len(associated_data or b"").to_bytes(4, 'big')) # Metadata length
if associated_data:
f.write(associated_data) # Metadata
f.write(ciphertext) # Encrypted content
def decrypt_file(self, input_path, output_path):
"""Decrypt file and return metadata"""
with open(input_path, 'rb') as f:
nonce = f.read(12)
metadata_len = int.from_bytes(f.read(4), 'big')
associated_data = f.read(metadata_len) if metadata_len > 0 else None
ciphertext = f.read()
try:
plaintext = self.aesgcm.decrypt(nonce, ciphertext, associated_data)
with open(output_path, 'wb') as f:
f.write(plaintext)
metadata = json.loads(associated_data.decode()) if associated_data else {}
return metadata
except InvalidTag:
raise ValueError("File decryption failed - file may be corrupted or tampered")
# Usage
file_manager = SecureFileManager()
# Encrypt file with metadata
file_manager.encrypt_file(
'document.pdf',
'document.pdf.enc',
metadata={'author': 'Alice', 'classification': 'confidential'}
)
# Decrypt file
metadata = file_manager.decrypt_file('document.pdf.enc', 'document_decrypted.pdf')
print(f"File metadata: {metadata}")from cryptography.hazmat.primitives.ciphers.aead import AESSIV
# AES-SIV is safe even with nonce reuse
key = AESSIV.generate_key(256) # 256-bit key for AES-128-SIV
aessiv = AESSIV(key)
# Encrypt same data multiple times (normally dangerous)
data = b"Repeated message"
associated_data = [b"context1", b"context2"]
# These encryptions are safe even though input is identical
ciphertext1 = aessiv.encrypt(data, associated_data)
ciphertext2 = aessiv.encrypt(data, associated_data)
print(f"Encryption 1: {ciphertext1.hex()}")
print(f"Encryption 2: {ciphertext2.hex()}")
print(f"Same result: {ciphertext1 == ciphertext2}") # True - deterministic
# Decrypt both
decrypted1 = aessiv.decrypt(ciphertext1, associated_data)
decrypted2 = aessiv.decrypt(ciphertext2, associated_data)
print(f"Decrypted: {decrypted1}")from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305, AESSIV
from cryptography.exceptions import InvalidTag
import os
import struct
class UniversalAEAD:
ALGORITHMS = {
'AES-GCM': AESGCM,
'ChaCha20-Poly1305': ChaCha20Poly1305,
'AES-SIV': AESSIV
}
def __init__(self, algorithm='AES-GCM', key_size=256):
self.algorithm = algorithm
self.key_size = key_size
if algorithm == 'AES-GCM':
self.key = AESGCM.generate_key(key_size)
self.cipher = AESGCM(self.key)
elif algorithm == 'ChaCha20-Poly1305':
self.key = ChaCha20Poly1305.generate_key()
self.cipher = ChaCha20Poly1305(self.key)
elif algorithm == 'AES-SIV':
self.key = AESSIV.generate_key(key_size)
self.cipher = AESSIV(self.key)
def encrypt(self, data, associated_data=None):
"""Encrypt with algorithm identification"""
if self.algorithm == 'AES-SIV':
# AES-SIV doesn't use nonce
ciphertext = self.cipher.encrypt(data, [associated_data] if associated_data else None)
nonce = b''
else:
# Nonce-based algorithms
nonce = os.urandom(12)
ciphertext = self.cipher.encrypt(nonce, data, associated_data)
# Format: algorithm_id (1 byte) + nonce_len (1 byte) + nonce + ciphertext
algorithm_id = list(self.ALGORITHMS.keys()).index(self.algorithm)
header = struct.pack('BB', algorithm_id, len(nonce))
return header + nonce + ciphertext
def decrypt(self, encrypted_data, associated_data=None):
"""Decrypt with automatic algorithm detection"""
algorithm_id, nonce_len = struct.unpack('BB', encrypted_data[:2])
algorithm = list(self.ALGORITHMS.keys())[algorithm_id]
nonce = encrypted_data[2:2+nonce_len]
ciphertext = encrypted_data[2+nonce_len:]
if algorithm == 'AES-SIV':
return self.cipher.decrypt(ciphertext, [associated_data] if associated_data else None)
else:
return self.cipher.decrypt(nonce, ciphertext, associated_data)
# Usage
# Try different algorithms
for alg in ['AES-GCM', 'ChaCha20-Poly1305', 'AES-SIV']:
aead = UniversalAEAD(alg)
data = b"Test message for " + alg.encode()
metadata = b"algorithm:" + alg.encode()
encrypted = aead.encrypt(data, metadata)
decrypted = aead.decrypt(encrypted, metadata)
print(f"{alg}: {decrypted}")Install with Tessl CLI
npx tessl i tessl/pypi-cryptography