Key Derivation Functions

Key Derivation Functions (KDFs) for password hashing and key derivation.


Password-Based KDFs

PBKDF2 (Password-Based Key Derivation Function 2)

$$ DK = PBKDF2(password, salt, iterations, dkLen) $$

Algorithm:

$$ \begin{aligned} T_i &= F(password, salt, iterations, i) \ F(password, salt, c, i) &= U_1 \oplus U_2 \oplus \cdots \oplus U_c \end{aligned} $$

Where:

$$ \begin{aligned} U_1 &= PRF(password, salt | INT(i)) \ U_j &= PRF(password, U_{j-1}) \end{aligned} $$

Iterations: Minimum 100,000 for PBKDF2-HMAC-SHA256

Argon2 (Winner of Password Hashing Competition)

$$ H = Argon2(password, salt, t, m, p) $$

Parameters:

  • $t$: Time cost (iterations)
  • $m$: Memory cost (KB)
  • $p$: Parallelism degree

Variants:

  • Argon2d: Data-dependent (GPU-resistant)
  • Argon2i: Data-independent (side-channel resistant)
  • Argon2id: Hybrid (recommended)

Memory-hard: Requires large memory, making GPU/ASIC attacks expensive.

scrypt

$$ DK = scrypt(password, salt, N, r, p, dkLen) $$

Parameters:

  • $N$: CPU/memory cost (power of 2)
  • $r$: Block size
  • $p$: Parallelization

Memory required: $\approx 128 \cdot N \cdot r$ bytes


Python Implementation

 1from argon2 import PasswordHasher
 2
 3ph = PasswordHasher(
 4    time_cost=2,        # iterations
 5    memory_cost=65536,  # 64 MB
 6    parallelism=4,      # threads
 7    hash_len=32,        # output length
 8    salt_len=16         # salt length
 9)
10
11# Hash password
12password = "user_password"
13hash = ph.hash(password)
14
15# Verify password
16try:
17    ph.verify(hash, password)
18    print("✅ Password correct!")
19except:
20    print("❌ Password incorrect!")
21
22# Check if rehash needed (params changed)
23if ph.check_needs_rehash(hash):
24    hash = ph.hash(password)

PBKDF2

 1from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
 2from cryptography.hazmat.primitives import hashes
 3import os
 4
 5password = b"user_password"
 6salt = os.urandom(16)
 7
 8kdf = PBKDF2HMAC(
 9    algorithm=hashes.SHA256(),
10    length=32,
11    salt=salt,
12    iterations=100000,
13)
14
15key = kdf.derive(password)
16
17# Verify
18kdf2 = PBKDF2HMAC(
19    algorithm=hashes.SHA256(),
20    length=32,
21    salt=salt,
22    iterations=100000,
23)
24
25try:
26    kdf2.verify(password, key)
27    print("✅ Password correct!")
28except:
29    print("❌ Password incorrect!")

scrypt

 1from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
 2
 3password = b"user_password"
 4salt = os.urandom(16)
 5
 6kdf = Scrypt(
 7    salt=salt,
 8    length=32,
 9    n=2**14,  # CPU/memory cost
10    r=8,      # block size
11    p=1,      # parallelization
12)
13
14key = kdf.derive(password)

Extract-Expand KDFs

HKDF (HMAC-based KDF)

Extract:

$$ PRK = HKDF\text{-}Extract(salt, IKM) $$

Expand:

$$ OKM = HKDF\text{-}Expand(PRK, info, L) $$

Use case: Derive multiple keys from shared secret (e.g., after ECDH)

 1from cryptography.hazmat.primitives.kdf.hkdf import HKDF
 2from cryptography.hazmat.primitives import hashes
 3
 4# Shared secret from ECDH
 5shared_secret = b"..."
 6
 7# Derive encryption and MAC keys
 8hkdf = HKDF(
 9    algorithm=hashes.SHA256(),
10    length=64,  # 32 for enc + 32 for MAC
11    salt=None,
12    info=b'handshake data',
13)
14
15key_material = hkdf.derive(shared_secret)
16enc_key = key_material[:32]
17mac_key = key_material[32:]

Comparison

AlgorithmTypeMemory-HardSpeedUse Case
Argon2idPasswordYesSlowPassword hashing (best)
scryptPasswordYesSlowPassword hashing
PBKDF2PasswordNoSlowLegacy, still acceptable
bcryptPasswordNoSlowPassword hashing
HKDFExtract-expandNoFastKey derivation from shared secret

Security Recommendations

Password Hashing

  1. Use Argon2id (or scrypt if unavailable)
  2. Minimum parameters:
    • Argon2: time_cost=2, memory_cost=65536 (64MB), parallelism=4
    • scrypt: N=2^14, r=8, p=1
    • PBKDF2: iterations=100000+
  3. Always use salt (random, unique per password)
  4. Salt length: 16+ bytes

Key Derivation

  1. Use HKDF for deriving keys from shared secrets
  2. Include context in info parameter
  3. Separate keys for different purposes

Related Snippets