0
# Key Derivation Functions
1
2
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.
3
4
## Core Imports
5
6
```python
7
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
8
from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand
9
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
10
from cryptography.hazmat.primitives.kdf.argon2 import Argon2
11
from cryptography.hazmat.primitives import hashes
12
```
13
14
## Capabilities
15
16
### PBKDF2 (Password-Based Key Derivation Function 2)
17
18
```python { .api }
19
class PBKDF2HMAC:
20
def __init__(self, algorithm, length: int, salt: bytes, iterations: int, backend=None):
21
"""
22
PBKDF2 with HMAC.
23
24
Args:
25
algorithm: Hash algorithm (hashes.SHA256(), etc.)
26
length (int): Output key length in bytes
27
salt (bytes): Random salt (16+ bytes recommended)
28
iterations (int): Number of iterations (100,000+ recommended)
29
"""
30
31
def derive(self, key_material: bytes) -> bytes:
32
"""
33
Derive key from password.
34
35
Args:
36
key_material (bytes): Password or key material
37
38
Returns:
39
bytes: Derived key
40
"""
41
42
def verify(self, key_material: bytes, expected_key: bytes) -> None:
43
"""
44
Verify password against expected key.
45
46
Raises:
47
InvalidKey: If verification fails
48
"""
49
```
50
51
### HKDF (HMAC-based Key Derivation Function)
52
53
```python { .api }
54
class HKDF:
55
def __init__(self, algorithm, length: int, salt: bytes = None, info: bytes = None, backend=None):
56
"""
57
HKDF extract-and-expand.
58
59
Args:
60
algorithm: Hash algorithm
61
length (int): Output length in bytes
62
salt (bytes, optional): Salt for extraction
63
info (bytes, optional): Context info for expansion
64
"""
65
66
def derive(self, key_material: bytes) -> bytes:
67
"""Derive key from input key material"""
68
69
class HKDFExpand:
70
def __init__(self, algorithm, length: int, info: bytes = None, backend=None):
71
"""HKDF expand-only (when you already have a PRK)"""
72
73
def derive(self, key_material: bytes) -> bytes:
74
"""Expand pseudorandom key"""
75
```
76
77
### Scrypt
78
79
```python { .api }
80
class Scrypt:
81
def __init__(self, algorithm, length: int, salt: bytes, n: int, r: int, p: int, backend=None):
82
"""
83
Scrypt key derivation function.
84
85
Args:
86
algorithm: Hash algorithm (usually SHA256)
87
length (int): Output length
88
salt (bytes): Random salt
89
n (int): CPU/memory cost (power of 2, e.g., 2**14)
90
r (int): Block size (e.g., 8)
91
p (int): Parallelization factor (e.g., 1)
92
"""
93
94
def derive(self, key_material: bytes) -> bytes:
95
"""Derive key using Scrypt"""
96
97
def verify(self, key_material: bytes, expected_key: bytes) -> None:
98
"""Verify password"""
99
```
100
101
### Argon2
102
103
```python { .api }
104
class Argon2:
105
def __init__(self, time_cost: int, memory_cost: int, parallelism: int,
106
hash_len: int, salt: bytes, type=Argon2.Type.I, backend=None):
107
"""
108
Argon2 password hashing.
109
110
Args:
111
time_cost (int): Number of iterations
112
memory_cost (int): Memory usage in KiB
113
parallelism (int): Number of parallel threads
114
hash_len (int): Output hash length
115
salt (bytes): Random salt
116
type: Argon2 variant (Type.I, Type.D, Type.ID)
117
"""
118
119
def derive(self, key_material: bytes) -> bytes:
120
"""Derive key using Argon2"""
121
122
def verify(self, key_material: bytes, expected_key: bytes) -> None:
123
"""Verify password"""
124
125
class Type:
126
I = "argon2i" # Side-channel resistant
127
D = "argon2d" # GPU-resistant
128
ID = "argon2id" # Hybrid (recommended)
129
```
130
131
## Usage Examples
132
133
### Password Hashing with PBKDF2
134
135
```python
136
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
137
from cryptography.hazmat.primitives import hashes
138
from cryptography.exceptions import InvalidKey
139
import os
140
141
def hash_password(password: str) -> tuple[bytes, bytes]:
142
"""Hash password for storage"""
143
salt = os.urandom(16)
144
kdf = PBKDF2HMAC(
145
algorithm=hashes.SHA256(),
146
length=32,
147
salt=salt,
148
iterations=100000, # OWASP minimum
149
)
150
key = kdf.derive(password.encode())
151
return key, salt
152
153
def verify_password(password: str, stored_key: bytes, salt: bytes) -> bool:
154
"""Verify password against stored hash"""
155
kdf = PBKDF2HMAC(
156
algorithm=hashes.SHA256(),
157
length=32,
158
salt=salt,
159
iterations=100000,
160
)
161
try:
162
kdf.verify(password.encode(), stored_key)
163
return True
164
except InvalidKey:
165
return False
166
167
# Usage
168
password = "user_secure_password"
169
key, salt = hash_password(password)
170
print(f"Stored key: {key.hex()}")
171
print(f"Salt: {salt.hex()}")
172
173
# Verify password
174
is_valid = verify_password(password, key, salt)
175
print(f"Password valid: {is_valid}")
176
```
177
178
### Key Derivation with HKDF
179
180
```python
181
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
182
from cryptography.hazmat.primitives import hashes
183
import os
184
185
# Derive multiple keys from shared secret
186
shared_secret = os.urandom(32) # From key exchange
187
salt = os.urandom(16)
188
189
# Derive encryption key
190
hkdf_enc = HKDF(
191
algorithm=hashes.SHA256(),
192
length=32,
193
salt=salt,
194
info=b'encryption key',
195
)
196
encryption_key = hkdf_enc.derive(shared_secret)
197
198
# Derive MAC key
199
hkdf_mac = HKDF(
200
algorithm=hashes.SHA256(),
201
length=32,
202
salt=salt,
203
info=b'authentication key',
204
)
205
mac_key = hkdf_mac.derive(shared_secret)
206
207
print(f"Encryption key: {encryption_key.hex()}")
208
print(f"MAC key: {mac_key.hex()}")
209
```
210
211
### Secure Password Storage with Argon2
212
213
```python
214
from cryptography.hazmat.primitives.kdf.argon2 import Argon2
215
from cryptography.exceptions import InvalidKey
216
import os
217
218
def argon2_hash_password(password: str) -> tuple[bytes, bytes]:
219
"""Hash password with Argon2id"""
220
salt = os.urandom(16)
221
argon2 = Argon2(
222
time_cost=3, # Number of iterations
223
memory_cost=65536, # 64 MB memory usage
224
parallelism=1, # Single thread
225
hash_len=32, # 32-byte output
226
salt=salt,
227
type=Argon2.Type.ID # Argon2id (recommended)
228
)
229
key = argon2.derive(password.encode())
230
return key, salt
231
232
# Hash password
233
password = "strong_user_password_123"
234
hashed_key, salt = argon2_hash_password(password)
235
print(f"Argon2 hash: {hashed_key.hex()}")
236
```
237
238
## Security Considerations
239
240
- **Salt Usage**: Always use random salts for password hashing
241
- **Iteration Count**: Use sufficient iterations (PBKDF2: 100k+, Scrypt/Argon2: adjust for ~100ms)
242
- **Algorithm Selection**: Prefer Argon2 > Scrypt > PBKDF2 for password hashing
243
- **Memory Hard**: Scrypt and Argon2 resist hardware attacks better than PBKDF2
244
- **Key Stretching**: Essential for converting low-entropy passwords to keys
245
- **Context Separation**: Use different info/context for different derived keys