CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cryptography

Cryptographic recipes and primitives for Python developers

Pending
Overview
Eval results
Files

symmetric-ciphers.mddocs/

Symmetric Ciphers

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.

Core Imports

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

Capabilities

Cipher Interface

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

Cipher Algorithms

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 16

Cipher Modes

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

Usage Examples

Basic AES-CBC Encryption

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

AES-CTR Mode (Stream Cipher)

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

ChaCha20 Stream Cipher

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

File Encryption with AES-CBC

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

Security Considerations

  • Mode Selection: Never use ECB mode for real applications
  • IV/Nonce Management: Always use random, unique IVs/nonces
  • Padding: Required for block ciphers with non-block-sized data
  • Authentication: Low-level ciphers don't provide authentication - use AEAD instead
  • Key Management: Use proper key derivation, never reuse keys inappropriately
  • Stream Cipher Risks: Never reuse key/nonce pairs with stream ciphers
  • High-Level APIs: Prefer Fernet or AEAD ciphers for most applications

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