Cryptographic recipes and primitives for Python developers
—
High-level symmetric encryption using authenticated encryption. Fernet provides secure, simple encryption/decryption with built-in authentication, timestamp verification, and key rotation support. It uses AES-128 in CBC mode with HMAC-SHA256 for authentication.
from cryptography.fernet import Fernet, MultiFernet, InvalidTokenThe Fernet class provides symmetric authenticated encryption with a simple API that handles all cryptographic details automatically.
class Fernet:
def __init__(self, key: bytes | str, backend: typing.Any = None):
"""
Initialize Fernet with a 32-byte base64url-encoded key.
Args:
key (bytes | str): 32-byte base64url-encoded key from generate_key()
backend (typing.Any, optional): Cryptographic backend (usually None)
"""
@classmethod
def generate_key(cls) -> bytes:
"""
Generate a fresh fernet key for encryption.
Returns:
bytes: 32-byte base64url-encoded key suitable for Fernet()
"""
def encrypt(self, data: bytes) -> bytes:
"""
Encrypt data with current timestamp.
Args:
data (bytes): Plaintext data to encrypt
Returns:
bytes: Encrypted token with embedded timestamp
"""
def encrypt_at_time(self, data: bytes, current_time: int) -> bytes:
"""
Encrypt data with a specific timestamp.
Args:
data (bytes): Plaintext data to encrypt
current_time (int): Unix timestamp to embed in token
Returns:
bytes: Encrypted token with specified timestamp
"""
def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes:
"""
Decrypt a token, optionally verifying age.
Args:
token (bytes): Encrypted token from encrypt()
ttl (int, optional): Maximum age in seconds
Returns:
bytes: Decrypted plaintext data
Raises:
InvalidToken: If token is invalid, expired, or too old
"""
def decrypt_at_time(self, token: bytes | str, ttl: int, current_time: int) -> bytes:
"""
Decrypt a token at a specific time with TTL verification.
Args:
token (bytes): Encrypted token from encrypt()
ttl (int): Maximum age in seconds
current_time (int): Unix timestamp to use as current time
Returns:
bytes: Decrypted plaintext data
Raises:
InvalidToken: If token is invalid, expired, or too old
"""
def extract_timestamp(self, token: bytes | str) -> int:
"""
Extract timestamp from a token without decrypting.
Args:
token (bytes): Encrypted token
Returns:
int: Unix timestamp when token was created
Raises:
InvalidToken: If token format is invalid
"""The MultiFernet class enables key rotation by maintaining multiple Fernet instances, encrypting with the first key and attempting decryption with all keys.
class MultiFernet:
def __init__(self, fernets: List[Fernet]):
"""
Initialize with multiple Fernet instances for key rotation.
Args:
fernets (List[Fernet]): List of Fernet instances, first used for encryption
"""
def encrypt(self, msg: bytes) -> bytes:
"""
Encrypt message using the first Fernet instance.
Args:
msg (bytes): Plaintext data to encrypt
Returns:
bytes: Encrypted token
"""
def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes:
"""
Encrypt message at specific time using first Fernet instance.
Args:
msg (bytes): Plaintext data to encrypt
current_time (int): Unix timestamp to embed
Returns:
bytes: Encrypted token with specified timestamp
"""
def decrypt(self, msg: bytes, ttl: int = None) -> bytes:
"""
Decrypt message trying each Fernet instance until successful.
Args:
msg (bytes): Encrypted token
ttl (int, optional): Maximum age in seconds
Returns:
bytes: Decrypted plaintext data
Raises:
InvalidToken: If no Fernet instance can decrypt the token
"""
def decrypt_at_time(self, msg: bytes, ttl: int, current_time: int) -> bytes:
"""
Decrypt at specific time trying each Fernet instance.
Args:
msg (bytes): Encrypted token
ttl (int): Maximum age in seconds
current_time (int): Unix timestamp as current time
Returns:
bytes: Decrypted plaintext data
Raises:
InvalidToken: If no Fernet instance can decrypt the token
"""
def rotate(self, msg: bytes) -> bytes:
"""
Re-encrypt message with the first (newest) key.
Args:
msg (bytes): Token encrypted with any of the Fernet instances
Returns:
bytes: Token re-encrypted with first Fernet instance
Raises:
InvalidToken: If message cannot be decrypted with any key
"""
def extract_timestamp(self, msg: bytes) -> int:
"""
Extract timestamp from token using any available key.
Args:
msg (bytes): Encrypted token
Returns:
int: Unix timestamp when token was created
Raises:
InvalidToken: If no key can validate the token format
"""class InvalidToken(Exception):
"""
Raised when a token is invalid, malformed, expired, or fails authentication.
This includes cases where:
- Token format is incorrect
- Authentication check fails
- Token is older than specified TTL
- Token timestamp is in the future (with clock skew tolerance)
"""from cryptography.fernet import Fernet
# Generate a key
key = Fernet.generate_key()
fernet = Fernet(key)
# Encrypt sensitive data
sensitive_data = b"user_id:12345,session:abc123"
token = fernet.encrypt(sensitive_data)
# Later, decrypt the data
try:
decrypted_data = fernet.decrypt(token)
print(decrypted_data) # b"user_id:12345,session:abc123"
except InvalidToken:
print("Invalid or expired token")from cryptography.fernet import Fernet, InvalidToken
import time
key = Fernet.generate_key()
fernet = Fernet(key)
# Create a token
data = b"temporary data"
token = fernet.encrypt(data)
# Decrypt with TTL - only valid for 60 seconds
try:
decrypted = fernet.decrypt(token, ttl=60)
print("Token is fresh:", decrypted)
except InvalidToken:
print("Token expired or invalid")
# Check token age without decrypting
timestamp = fernet.extract_timestamp(token)
age = time.time() - timestamp
print(f"Token age: {age} seconds")from cryptography.fernet import Fernet, MultiFernet
# Current encryption key
new_key = Fernet.generate_key()
new_fernet = Fernet(new_key)
# Previous keys for decryption
old_key1 = Fernet.generate_key() # Previous key
old_key2 = Fernet.generate_key() # Even older key
old_fernet1 = Fernet(old_key1)
old_fernet2 = Fernet(old_key2)
# MultiFernet with new key first (for encryption)
multi_fernet = MultiFernet([
new_fernet, # Used for encryption
old_fernet1, # Can decrypt old tokens
old_fernet2 # Can decrypt even older tokens
])
# Encrypt with new key
token = multi_fernet.encrypt(b"data")
# Can decrypt tokens encrypted with any key
decrypted = multi_fernet.decrypt(token)
# Re-encrypt old token with new key
old_token = old_fernet1.encrypt(b"old data")
rotated_token = multi_fernet.rotate(old_token) # Now encrypted with new_fernetfrom cryptography.fernet import Fernet, InvalidToken
import json
import time
class SecureSession:
def __init__(self, secret_key):
self.fernet = Fernet(secret_key)
def create_session_token(self, user_id, session_data):
"""Create encrypted session token"""
payload = {
'user_id': user_id,
'data': session_data,
'created': time.time()
}
json_payload = json.dumps(payload).encode()
return self.fernet.encrypt(json_payload)
def validate_session(self, token, max_age=3600):
"""Validate and extract session data"""
try:
# Decrypt with TTL check
decrypted = self.fernet.decrypt(token, ttl=max_age)
payload = json.loads(decrypted.decode())
return payload
except InvalidToken:
return None
# Usage
session_key = Fernet.generate_key()
session_manager = SecureSession(session_key)
# Create session
token = session_manager.create_session_token(
user_id=12345,
session_data={'role': 'admin', 'permissions': ['read', 'write']}
)
# Validate session (within 1 hour)
session_data = session_manager.validate_session(token, max_age=3600)
if session_data:
print(f"Valid session for user {session_data['user_id']}")
else:
print("Invalid or expired session")Install with Tessl CLI
npx tessl i tessl/pypi-cryptography