Cryptographic recipes and primitives for Python developers
—
Password-based and key-based derivation functions for generating cryptographic keys from passwords or other key material. Essential for secure password storage and key management.
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives.kdf.argon2 import Argon2
from cryptography.hazmat.primitives import hashesclass PBKDF2HMAC:
def __init__(self, algorithm, length: int, salt: bytes, iterations: int, backend=None):
"""
PBKDF2 with HMAC.
Args:
algorithm: Hash algorithm (hashes.SHA256(), etc.)
length (int): Output key length in bytes
salt (bytes): Random salt (16+ bytes recommended)
iterations (int): Number of iterations (100,000+ recommended)
"""
def derive(self, key_material: bytes) -> bytes:
"""
Derive key from password.
Args:
key_material (bytes): Password or key material
Returns:
bytes: Derived key
"""
def verify(self, key_material: bytes, expected_key: bytes) -> None:
"""
Verify password against expected key.
Raises:
InvalidKey: If verification fails
"""class HKDF:
def __init__(self, algorithm, length: int, salt: bytes = None, info: bytes = None, backend=None):
"""
HKDF extract-and-expand.
Args:
algorithm: Hash algorithm
length (int): Output length in bytes
salt (bytes, optional): Salt for extraction
info (bytes, optional): Context info for expansion
"""
def derive(self, key_material: bytes) -> bytes:
"""Derive key from input key material"""
class HKDFExpand:
def __init__(self, algorithm, length: int, info: bytes = None, backend=None):
"""HKDF expand-only (when you already have a PRK)"""
def derive(self, key_material: bytes) -> bytes:
"""Expand pseudorandom key"""class Scrypt:
def __init__(self, algorithm, length: int, salt: bytes, n: int, r: int, p: int, backend=None):
"""
Scrypt key derivation function.
Args:
algorithm: Hash algorithm (usually SHA256)
length (int): Output length
salt (bytes): Random salt
n (int): CPU/memory cost (power of 2, e.g., 2**14)
r (int): Block size (e.g., 8)
p (int): Parallelization factor (e.g., 1)
"""
def derive(self, key_material: bytes) -> bytes:
"""Derive key using Scrypt"""
def verify(self, key_material: bytes, expected_key: bytes) -> None:
"""Verify password"""class Argon2:
def __init__(self, time_cost: int, memory_cost: int, parallelism: int,
hash_len: int, salt: bytes, type=Argon2.Type.I, backend=None):
"""
Argon2 password hashing.
Args:
time_cost (int): Number of iterations
memory_cost (int): Memory usage in KiB
parallelism (int): Number of parallel threads
hash_len (int): Output hash length
salt (bytes): Random salt
type: Argon2 variant (Type.I, Type.D, Type.ID)
"""
def derive(self, key_material: bytes) -> bytes:
"""Derive key using Argon2"""
def verify(self, key_material: bytes, expected_key: bytes) -> None:
"""Verify password"""
class Type:
I = "argon2i" # Side-channel resistant
D = "argon2d" # GPU-resistant
ID = "argon2id" # Hybrid (recommended)from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidKey
import os
def hash_password(password: str) -> tuple[bytes, bytes]:
"""Hash password for storage"""
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000, # OWASP minimum
)
key = kdf.derive(password.encode())
return key, salt
def verify_password(password: str, stored_key: bytes, salt: bytes) -> bool:
"""Verify password against stored hash"""
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
)
try:
kdf.verify(password.encode(), stored_key)
return True
except InvalidKey:
return False
# Usage
password = "user_secure_password"
key, salt = hash_password(password)
print(f"Stored key: {key.hex()}")
print(f"Salt: {salt.hex()}")
# Verify password
is_valid = verify_password(password, key, salt)
print(f"Password valid: {is_valid}")from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
import os
# Derive multiple keys from shared secret
shared_secret = os.urandom(32) # From key exchange
salt = os.urandom(16)
# Derive encryption key
hkdf_enc = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
info=b'encryption key',
)
encryption_key = hkdf_enc.derive(shared_secret)
# Derive MAC key
hkdf_mac = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
info=b'authentication key',
)
mac_key = hkdf_mac.derive(shared_secret)
print(f"Encryption key: {encryption_key.hex()}")
print(f"MAC key: {mac_key.hex()}")from cryptography.hazmat.primitives.kdf.argon2 import Argon2
from cryptography.exceptions import InvalidKey
import os
def argon2_hash_password(password: str) -> tuple[bytes, bytes]:
"""Hash password with Argon2id"""
salt = os.urandom(16)
argon2 = Argon2(
time_cost=3, # Number of iterations
memory_cost=65536, # 64 MB memory usage
parallelism=1, # Single thread
hash_len=32, # 32-byte output
salt=salt,
type=Argon2.Type.ID # Argon2id (recommended)
)
key = argon2.derive(password.encode())
return key, salt
# Hash password
password = "strong_user_password_123"
hashed_key, salt = argon2_hash_password(password)
print(f"Argon2 hash: {hashed_key.hex()}")Install with Tessl CLI
npx tessl i tessl/pypi-cryptography