Cryptographic recipes and primitives for Python developers
—
Low-level symmetric encryption algorithms and cipher modes. These are building blocks for custom cryptographic protocols - for most applications, use high-level APIs like Fernet or AEAD ciphers instead.
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modesThe Cipher class provides a consistent interface for all symmetric ciphers.
class Cipher:
def __init__(self, algorithm, mode, backend=None):
"""
Create cipher with algorithm and mode.
Args:
algorithm: Cipher algorithm (AES(), ChaCha20(), etc.)
mode: Cipher mode (CBC(), CTR(), GCM(), etc.)
backend: Cryptographic backend (usually None)
"""
def encryptor(self) -> CipherContext:
"""
Create encryption context.
Returns:
CipherContext: Context for encryption operations
"""
def decryptor(self) -> CipherContext:
"""
Create decryption context.
Returns:
CipherContext: Context for decryption operations
"""
class CipherContext:
def update(self, data: bytes) -> bytes:
"""
Process data through cipher.
Args:
data (bytes): Data to encrypt/decrypt
Returns:
bytes: Processed data
"""
def finalize(self) -> bytes:
"""
Finalize cipher operation.
Returns:
bytes: Final processed data
"""class AES:
def __init__(self, key: bytes):
"""
AES algorithm with 128, 192, or 256-bit key.
Args:
key (bytes): 16, 24, or 32-byte key
"""
@property
def block_size(self) -> int:
"""AES block size (16 bytes)"""
return 16
class ChaCha20:
def __init__(self, key: bytes, nonce: bytes):
"""
ChaCha20 stream cipher.
Args:
key (bytes): 32-byte key
nonce (bytes): 16-byte nonce
"""
class Camellia:
def __init__(self, key: bytes):
"""
Camellia block cipher (128, 192, or 256-bit key).
Args:
key (bytes): 16, 24, or 32-byte key
"""
@property
def block_size(self) -> int:
return 16
class SM4:
def __init__(self, key: bytes):
"""
SM4 block cipher (Chinese national standard).
Args:
key (bytes): 16-byte key
"""
@property
def block_size(self) -> int:
return 16class ECB:
"""
Electronic Codebook mode (insecure - don't use).
Each block encrypted independently.
"""
class CBC:
def __init__(self, initialization_vector: bytes):
"""
Cipher Block Chaining mode.
Args:
initialization_vector (bytes): IV same size as block size
"""
class CTR:
def __init__(self, nonce: bytes):
"""
Counter mode (stream cipher mode).
Args:
nonce (bytes): Nonce for counter initialization
"""
class CFB:
def __init__(self, initialization_vector: bytes):
"""
Cipher Feedback mode.
Args:
initialization_vector (bytes): IV same size as block size
"""
class OFB:
def __init__(self, initialization_vector: bytes):
"""
Output Feedback mode.
Args:
initialization_vector (bytes): IV same size as block size
"""
class GCM:
def __init__(self, initialization_vector: bytes, tag: bytes = None, min_tag_length: int = 16):
"""
Galois/Counter Mode (authenticated encryption).
Args:
initialization_vector (bytes): IV (96 bits recommended)
tag (bytes, optional): Authentication tag for decryption
min_tag_length (int): Minimum tag length (4-16 bytes)
"""from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import os
# Generate key and IV
key = os.urandom(32) # 256-bit key
iv = os.urandom(16) # 128-bit IV
# Create cipher
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
# Encrypt data
plaintext = b"Secret message that needs padding for block cipher"
# Pad data to block size
padder = padding.PKCS7(128).padder() # 128-bit block size
padded_data = padder.update(plaintext)
padded_data += padder.finalize()
# Encrypt
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
print(f"Encrypted: {ciphertext.hex()}")
# Decrypt
decryptor = cipher.decryptor()
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
# Remove padding
unpadder = padding.PKCS7(128).unpadder()
plaintext_recovered = unpadder.update(padded_plaintext)
plaintext_recovered += unpadder.finalize()
print(f"Decrypted: {plaintext_recovered}")from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import os
# CTR mode doesn't require padding
key = os.urandom(32)
nonce = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CTR(nonce))
plaintext = b"This message doesn't need padding in CTR mode"
# Encrypt
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
# Decrypt
decryptor = cipher.decryptor()
decrypted_text = decryptor.update(ciphertext) + decryptor.finalize()
print(f"Original: {plaintext}")
print(f"Decrypted: {decrypted_text}")
print(f"Match: {plaintext == decrypted_text}")from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
import os
# ChaCha20 is a stream cipher
key = os.urandom(32) # 256-bit key
nonce = os.urandom(16) # 128-bit nonce
cipher = Cipher(algorithms.ChaCha20(key, nonce), mode=None)
message = b"Stream cipher message - no padding needed"
# Encrypt
encryptor = cipher.encryptor()
ciphertext = encryptor.update(message) + encryptor.finalize()
# Decrypt
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
print(f"ChaCha20 encryption successful: {message == plaintext}")from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import os
def encrypt_file(input_path, output_path, key):
"""Encrypt file with AES-CBC"""
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
with open(input_path, 'rb') as infile, open(output_path, 'wb') as outfile:
# Write IV first
outfile.write(iv)
# Encrypt file in chunks with padding
while True:
chunk = infile.read(8192)
if not chunk:
# Final chunk with padding
padded_chunk = padder.finalize()
if padded_chunk:
outfile.write(encryptor.update(padded_chunk))
break
if len(chunk) < 8192:
# Last chunk - apply padding
padded_chunk = padder.update(chunk) + padder.finalize()
outfile.write(encryptor.update(padded_chunk))
break
else:
# Full chunk - no padding yet
padded_chunk = padder.update(chunk)
outfile.write(encryptor.update(padded_chunk))
# Finalize encryption
outfile.write(encryptor.finalize())
def decrypt_file(input_path, output_path, key):
"""Decrypt file encrypted with AES-CBC"""
with open(input_path, 'rb') as infile:
# Read IV
iv = infile.read(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
# Read and decrypt remaining data
ciphertext = infile.read()
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
# Remove padding
unpadder = padding.PKCS7(128).unpadder()
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
with open(output_path, 'wb') as outfile:
outfile.write(plaintext)
# Usage
key = os.urandom(32)
# Create test file
with open('test.txt', 'w') as f:
f.write("This is a test file for encryption\n" * 100)
# Encrypt file
encrypt_file('test.txt', 'test.txt.enc', key)
print("File encrypted")
# Decrypt file
decrypt_file('test.txt.enc', 'test_decrypted.txt', key)
print("File decrypted")
# Verify
with open('test.txt', 'rb') as f1, open('test_decrypted.txt', 'rb') as f2:
print(f"Files match: {f1.read() == f2.read()}")Install with Tessl CLI
npx tessl i tessl/pypi-cryptography