CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cryptography

Cryptographic recipes and primitives for Python developers

Pending
Overview
Eval results
Files

symmetric-encryption.mddocs/

Symmetric Encryption (Fernet)

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.

Core Imports

from cryptography.fernet import Fernet, MultiFernet, InvalidToken

Capabilities

Basic Fernet Encryption

The 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
        """

Key Rotation with MultiFernet

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
        """

Exception Handling

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)
    """

Usage Examples

Basic Encryption/Decryption

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")

Time-based Token Validation

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")

Key Rotation

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_fernet

Secure Session Management

from 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")

Security Considerations

  • Key Management: Store Fernet keys securely, never hardcode them
  • Token Storage: Treat encrypted tokens as sensitive data
  • TTL Usage: Always use TTL for time-sensitive data
  • Key Rotation: Regularly rotate keys using MultiFernet
  • Clock Skew: Fernet allows 60 seconds of clock skew tolerance
  • Token Size: Encrypted tokens are larger than original data (base64 overhead + metadata)

Install with Tessl CLI

npx tessl i tessl/pypi-cryptography

docs

aead-ciphers.md

asymmetric-cryptography.md

hash-functions.md

index.md

key-derivation.md

key-serialization.md

message-authentication.md

symmetric-ciphers.md

symmetric-encryption.md

two-factor-auth.md

utilities.md

x509-certificates.md

tile.json