Key Derivation Functions

Side-channel claim limit. Nothing on this page claims constant-time behaviour or side-channel resistance for any target: the side-channel harness covers nothing (finding A48) and no measurement exists. The Rust and C cores are designed for constant time and unmeasured; managed runtimes (JIT, GC, bounds checks) make no timing claim. Where this page names constant-time or side-channel techniques, in prose or in code, they are design intent or recommendations, not measured properties.

Key derivation functions (KDFs) transform passwords or other input material into cryptographic keys. MetaMUI implements modern KDFs for password hashing and key expansion.

Available Algorithms

Argon2

Memory-Hard Password Hashing

PBKDF2

Password-Based Key Derivation Function 2

HKDF

HMAC-Based Key Derivation Function

Algorithm Selection Guide

For Password Hashing

Recommended: Argon2id

For Legacy Systems

Use: PBKDF2

For Key Expansion

Use: HKDF

Usage Examples

Password Hashing with Argon2

from metamui_crypto import Argon2
import os

# Hash password for storage
password = b"user_password"
salt = os.urandom(16)  # Random salt

# Hash with recommended parameters
hash = Argon2.hash(
    password=password,
    salt=salt,
    time_cost=3,        # iterations
    memory_cost=65536,  # 64MB
    parallelism=4,      # threads
    hash_length=32      # output bytes
)

# Store salt + hash
stored = salt + hash

# Verify password
def verify_password(password, stored):
    salt = stored[:16]
    hash = stored[16:]
    
    computed = Argon2.hash(
        password=password,
        salt=salt,
        time_cost=3,
        memory_cost=65536,
        parallelism=4,
        hash_length=32
    )
    
    return computed == hash

Key Derivation with PBKDF2

from metamui_crypto import PBKDF2
import os

# Derive encryption key from password
password = b"user_password"
salt = os.urandom(32)

# PBKDF2 with SHA-256
key = PBKDF2.derive(
    password=password,
    salt=salt,
    iterations=100000,
    key_length=32,
    hash_function='sha256'
)

# Use derived key for encryption
from metamui_crypto import ChaCha20Poly1305
cipher = ChaCha20Poly1305(key)

Key Expansion with HKDF

from metamui_crypto import HKDF
import os

# Master key material
master_key = os.urandom(32)
salt = os.urandom(32)

# Derive multiple keys for different purposes
encryption_key = HKDF.derive(
    key_material=master_key,
    salt=salt,
    info=b"encryption",
    length=32,
    hash_function='sha256'
)

mac_key = HKDF.derive(
    key_material=master_key,
    salt=salt,
    info=b"mac",
    length=32,
    hash_function='sha256'
)

# Keys are cryptographically independent
assert encryption_key != mac_key

Secure Password Storage

from metamui_crypto import Argon2
import os
import json

class PasswordManager:
    def __init__(self):
        # Argon2 parameters
        self.time_cost = 3
        self.memory_cost = 65536
        self.parallelism = 4
        self.hash_length = 32
        self.salt_length = 16
    
    def hash_password(self, password: str) -> dict:
        """Hash password for storage"""
        salt = os.urandom(self.salt_length)
        
        hash = Argon2.hash(
            password=password.encode('utf-8'),
            salt=salt,
            time_cost=self.time_cost,
            memory_cost=self.memory_cost,
            parallelism=self.parallelism,
            hash_length=self.hash_length
        )
        
        # Return parameters with hash for future verification
        return {
            'hash': (salt + hash).hex(),
            'algorithm': 'argon2id',
            'time_cost': self.time_cost,
            'memory_cost': self.memory_cost,
            'parallelism': self.parallelism
        }
    
    def verify_password(self, password: str, stored: dict) -> bool:
        """Verify password against stored hash"""
        stored_bytes = bytes.fromhex(stored['hash'])
        salt = stored_bytes[:self.salt_length]
        expected_hash = stored_bytes[self.salt_length:]
        
        computed_hash = Argon2.hash(
            password=password.encode('utf-8'),
            salt=salt,
            time_cost=stored['time_cost'],
            memory_cost=stored['memory_cost'],
            parallelism=stored['parallelism'],
            hash_length=self.hash_length
        )
        
        # Constant-time comparison
        return computed_hash == expected_hash

Hierarchical Key Derivation

from metamui_crypto import HKDF
import os

class KeyHierarchy:
    def __init__(self, master_secret: bytes):
        self.master = master_secret
        
    def derive_user_key(self, user_id: str) -> bytes:
        """Derive key for specific user"""
        return HKDF.derive(
            key_material=self.master,
            salt=b"users",
            info=user_id.encode('utf-8'),
            length=32
        )
    
    def derive_session_key(self, user_key: bytes, session_id: str) -> bytes:
        """Derive key for specific session"""
        return HKDF.derive(
            key_material=user_key,
            salt=b"sessions",
            info=session_id.encode('utf-8'),
            length=32
        )

# Usage
hierarchy = KeyHierarchy(os.urandom(32))
user_key = hierarchy.derive_user_key("user123")
session_key = hierarchy.derive_session_key(user_key, "session456")

Security Parameters

Argon2 Recommendations

| Security Level | Time Cost | Memory Cost | Parallelism | |—————-|———–|————-|————-| | Interactive | 1 | 64 MB | 4 | | Moderate | 3 | 256 MB | 4 | | Sensitive | 5 | 1 GB | 8 |

PBKDF2 Iterations

| Year | Minimum Iterations | Recommended | |——|——————-|————-| | 2023 | 100,000 | 600,000 | | 2024 | 150,000 | 800,000 | | 2025 | 200,000 | 1,000,000 |

Performance Considerations

Argon2 Benchmarks

| Parameters | Time | Memory Usage | |————|——|————–| | t=1, m=64MB | 50ms | 64 MB | | t=3, m=256MB | 300ms | 256 MB | | t=5, m=1GB | 2s | 1 GB |

PBKDF2 Performance

| Iterations | SHA-256 | SHA-512 | |————|———|———| | 100,000 | 100ms | 150ms | | 500,000 | 500ms | 750ms | | 1,000,000 | 1s | 1.5s |

Best Practices

1. Use Appropriate Parameters

# Good: Sufficient security parameters
hash = Argon2.hash(
    password,
    salt=os.urandom(16),
    time_cost=3,
    memory_cost=65536,  # 64MB
    parallelism=4
)

# Bad: Weak parameters
# hash = Argon2.hash(
#     password,
#     salt=salt,
#     time_cost=1,      # Too fast
#     memory_cost=1024, # Too little memory
#     parallelism=1
# )

2. Always Use Salt

# Good: Random salt for each password
salt = os.urandom(16)
hash = Argon2.hash(password, salt=salt)

# Bad: No salt or fixed salt
# hash = Argon2.hash(password, salt=b"fixed")  # Vulnerable to rainbow tables

3. Store Parameters

# Good: Store all parameters for future compatibility
password_record = {
    'hash': hash.hex(),
    'salt': salt.hex(),
    'algorithm': 'argon2id',
    'version': 19,
    'time_cost': 3,
    'memory_cost': 65536,
    'parallelism': 4
}

# Allows parameter upgrades later
if password_record['time_cost'] < current_minimum:
    # Re-hash with stronger parameters on next login
    pass

4. Implement Rate Limiting

from time import time
from collections import defaultdict

class RateLimiter:
    def __init__(self, max_attempts=5, window=300):
        self.attempts = defaultdict(list)
        self.max_attempts = max_attempts
        self.window = window
    
    def check_rate_limit(self, identifier):
        now = time()
        # Clean old attempts
        self.attempts[identifier] = [
            t for t in self.attempts[identifier] 
            if now - t < self.window
        ]
        
        if len(self.attempts[identifier]) >= self.max_attempts:
            return False
            
        self.attempts[identifier].append(now)
        return True

Migration Strategies

From MD5/SHA1 Password Hashing

# Legacy system with weak hashing
# old_hash = hashlib.md5(password + salt).hexdigest()

# Migration approach
def migrate_password(password, old_hash, salt):
    # Verify against old system
    if hashlib.md5(password + salt).hexdigest() != old_hash:
        return None
    
    # Re-hash with Argon2
    new_salt = os.urandom(16)
    new_hash = Argon2.hash(
        password,
        salt=new_salt,
        time_cost=3,
        memory_cost=65536
    )
    
    return {
        'hash': new_hash,
        'salt': new_salt,
        'algorithm': 'argon2id',
        'migrated': True
    }

Upgrading PBKDF2 Parameters

def upgrade_pbkdf2_iterations(password, stored_hash, old_iterations):
    # Verify with old parameters
    computed = PBKDF2.derive(
        password=password,
        salt=stored_hash['salt'],
        iterations=old_iterations,
        key_length=32
    )
    
    if computed != stored_hash['hash']:
        return None
    
    # Re-derive with more iterations
    new_iterations = 1000000  # Current recommendation
    new_hash = PBKDF2.derive(
        password=password,
        salt=stored_hash['salt'],
        iterations=new_iterations,
        key_length=32
    )
    
    return {
        'hash': new_hash,
        'salt': stored_hash['salt'],
        'iterations': new_iterations
    }

Common Pitfalls

1. Using KDFs for Non-Password Data

# Bad: Using Argon2 for non-password data
# key = Argon2.hash(random_data)  # Wasteful

# Good: Use HKDF for key derivation
key = HKDF.derive(random_data, info=b"encryption")

2. Insufficient Entropy

# Bad: Weak password without policy
# password = "123456"

# Good: Enforce password requirements
import secrets
import string

def generate_strong_password(length=16):
    alphabet = string.ascii_letters + string.digits + string.punctuation
    return ''.join(secrets.choice(alphabet) for _ in range(length))

3. Timing Attacks

# Bad: Early return on mismatch
# if computed_hash != stored_hash:
#     return False  # Timing leak

# Good: Constant-time comparison
import hmac
return hmac.compare_digest(computed_hash, stored_hash)

Resources