CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cryptography

Cryptographic recipes and primitives for Python developers

Pending
Overview
Eval results
Files

key-derivation.mddocs/

Key Derivation Functions

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.

Core Imports

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 hashes

Capabilities

PBKDF2 (Password-Based Key Derivation Function 2)

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

HKDF (HMAC-based Key Derivation Function)

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

Scrypt

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

Argon2

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)

Usage Examples

Password Hashing with PBKDF2

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

Key Derivation with HKDF

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

Secure Password Storage with Argon2

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

Security Considerations

  • Salt Usage: Always use random salts for password hashing
  • Iteration Count: Use sufficient iterations (PBKDF2: 100k+, Scrypt/Argon2: adjust for ~100ms)
  • Algorithm Selection: Prefer Argon2 > Scrypt > PBKDF2 for password hashing
  • Memory Hard: Scrypt and Argon2 resist hardware attacks better than PBKDF2
  • Key Stretching: Essential for converting low-entropy passwords to keys
  • Context Separation: Use different info/context for different derived keys

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