0
# Key Derivation Functions
1
2
Password-based key derivation functions including PBKDF1, PBKDF2, and PKCS#12 KDF. These functions derive cryptographic keys from passwords using salt and iteration counts for security against brute-force attacks.
3
4
## Capabilities
5
6
### PBKDF2
7
8
Password-Based Key Derivation Function 2 (PBKDF2) as defined in RFC 2898, the most commonly used key derivation function.
9
10
```python { .api }
11
def pbkdf2(hash_algorithm: str, password: bytes, salt: bytes, iterations: int, key_length: int) -> bytes:
12
"""
13
Derive a key using PBKDF2.
14
15
Parameters:
16
- hash_algorithm: str - Hash algorithm ('sha1', 'sha224', 'sha256', 'sha384', 'sha512')
17
- password: bytes - Password to derive key from
18
- salt: bytes - Salt value (should be at least 8 bytes, preferably 16+)
19
- iterations: int - Number of iterations (minimum 1000, recommended 10000+)
20
- key_length: int - Length of derived key in bytes
21
22
Returns:
23
Derived key bytes
24
"""
25
26
def pbkdf2_iteration_calculator(hash_algorithm: str, key_length: int, target_ms: int = 100, quiet: bool = False) -> int:
27
"""
28
Calculate PBKDF2 iterations for a target computation time.
29
30
Parameters:
31
- hash_algorithm: str - Hash algorithm to benchmark
32
- key_length: int - Length of key to derive
33
- target_ms: int - Target computation time in milliseconds
34
- quiet: bool - Suppress progress output
35
36
Returns:
37
Number of iterations that achieves approximately target_ms computation time
38
"""
39
```
40
41
### PBKDF1
42
43
Password-Based Key Derivation Function 1 (PBKDF1) as defined in RFC 2898. Less secure than PBKDF2, included for compatibility.
44
45
```python { .api }
46
def pbkdf1(hash_algorithm: str, password: bytes, salt: bytes, iterations: int, key_length: int) -> bytes:
47
"""
48
Derive a key using PBKDF1 (legacy, less secure than PBKDF2).
49
50
Parameters:
51
- hash_algorithm: str - Hash algorithm ('sha1', 'md5')
52
- password: bytes - Password to derive key from
53
- salt: bytes - Salt value (8 bytes)
54
- iterations: int - Number of iterations
55
- key_length: int - Length of derived key (limited by hash output size)
56
57
Returns:
58
Derived key bytes
59
60
Note:
61
PBKDF1 is deprecated. Use PBKDF2 for new applications.
62
"""
63
```
64
65
### PKCS#12 KDF
66
67
PKCS#12 key derivation function used specifically for PKCS#12 files and some legacy applications.
68
69
```python { .api }
70
def pkcs12_kdf(hash_algorithm: str, password: bytes, salt: bytes, iterations: int, key_length: int, id_: int) -> bytes:
71
"""
72
Derive a key using PKCS#12 KDF.
73
74
Parameters:
75
- hash_algorithm: str - Hash algorithm ('sha1', 'sha224', 'sha256', 'sha384', 'sha512')
76
- password: bytes - Password to derive key from
77
- salt: bytes - Salt value
78
- iterations: int - Number of iterations
79
- key_length: int - Length of derived key in bytes
80
- id_: int - Purpose ID (1=key material, 2=IV, 3=MAC key)
81
82
Returns:
83
Derived key bytes
84
"""
85
```
86
87
## Usage Examples
88
89
### PBKDF2 Key Derivation
90
91
```python
92
from oscrypto.kdf import pbkdf2, pbkdf2_iteration_calculator
93
from oscrypto.util import rand_bytes
94
import os
95
96
# Generate secure random salt
97
salt = rand_bytes(16)
98
password = b"user_password_123"
99
100
# Calculate appropriate iteration count for ~100ms computation time
101
iterations = pbkdf2_iteration_calculator('sha256', 32, target_ms=100)
102
print(f"Using {iterations} iterations for ~100ms computation time")
103
104
# Derive AES-256 key (32 bytes)
105
key = pbkdf2('sha256', password, salt, iterations, 32)
106
107
print(f"Derived {len(key)} byte key")
108
print(f"Key: {key.hex()}")
109
```
110
111
### Secure Password Storage
112
113
```python
114
from oscrypto.kdf import pbkdf2
115
from oscrypto.util import rand_bytes, constant_compare
116
import hashlib
117
118
def hash_password(password: str) -> tuple:
119
"""Hash a password securely for storage."""
120
# Convert password to bytes
121
password_bytes = password.encode('utf-8')
122
123
# Generate random salt
124
salt = rand_bytes(16)
125
126
# Use strong iteration count
127
iterations = 100000
128
129
# Derive key and hash it for storage
130
key = pbkdf2('sha256', password_bytes, salt, iterations, 32)
131
132
return salt, iterations, key
133
134
def verify_password(password: str, stored_salt: bytes, stored_iterations: int, stored_key: bytes) -> bool:
135
"""Verify a password against stored hash."""
136
password_bytes = password.encode('utf-8')
137
138
# Derive key with same parameters
139
derived_key = pbkdf2('sha256', password_bytes, stored_salt, stored_iterations, 32)
140
141
# Use constant-time comparison
142
return constant_compare(derived_key, stored_key)
143
144
# Example usage
145
password = "my_secure_password"
146
147
# Store password
148
salt, iterations, key = hash_password(password)
149
print(f"Stored salt: {salt.hex()}")
150
print(f"Iterations: {iterations}")
151
152
# Verify password
153
is_valid = verify_password(password, salt, iterations, key)
154
print(f"Password valid: {is_valid}")
155
156
# Wrong password
157
is_valid = verify_password("wrong_password", salt, iterations, key)
158
print(f"Wrong password valid: {is_valid}")
159
```
160
161
### Key Derivation for Encryption
162
163
```python
164
from oscrypto.kdf import pbkdf2
165
from oscrypto.symmetric import aes_cbc_pkcs7_encrypt, aes_cbc_pkcs7_decrypt
166
from oscrypto.util import rand_bytes
167
168
def encrypt_with_password(data: bytes, password: str) -> tuple:
169
"""Encrypt data using password-derived key."""
170
# Convert password to bytes
171
password_bytes = password.encode('utf-8')
172
173
# Generate random salt for key derivation
174
salt = rand_bytes(16)
175
176
# Derive AES-256 key
177
key = pbkdf2('sha256', password_bytes, salt, 100000, 32)
178
179
# Encrypt data
180
ciphertext = aes_cbc_pkcs7_encrypt(key, data)
181
182
return salt, ciphertext
183
184
def decrypt_with_password(salt: bytes, ciphertext: bytes, password: str) -> bytes:
185
"""Decrypt data using password-derived key."""
186
password_bytes = password.encode('utf-8')
187
188
# Derive the same key
189
key = pbkdf2('sha256', password_bytes, salt, 100000, 32)
190
191
# Decrypt data
192
return aes_cbc_pkcs7_decrypt(key, ciphertext)
193
194
# Example usage
195
plaintext = b"Secret document contents"
196
password = "encryption_password_123"
197
198
# Encrypt
199
salt, ciphertext = encrypt_with_password(plaintext, password)
200
print(f"Encrypted {len(plaintext)} bytes to {len(ciphertext)} bytes")
201
202
# Decrypt
203
decrypted = decrypt_with_password(salt, ciphertext, password)
204
print(f"Decrypted: {decrypted}")
205
assert decrypted == plaintext
206
```
207
208
### Multiple Hash Algorithms
209
210
```python
211
from oscrypto.kdf import pbkdf2
212
from oscrypto.util import rand_bytes
213
214
password = b"test_password"
215
salt = rand_bytes(16)
216
iterations = 10000
217
key_length = 32
218
219
# Test different hash algorithms
220
algorithms = ['sha1', 'sha256', 'sha384', 'sha512']
221
222
for algorithm in algorithms:
223
key = pbkdf2(algorithm, password, salt, iterations, key_length)
224
print(f"{algorithm.upper()}: {key[:8].hex()}...")
225
```
226
227
### Iteration Count Tuning
228
229
```python
230
from oscrypto.kdf import pbkdf2_iteration_calculator
231
import time
232
233
# Test different target computation times
234
targets = [50, 100, 200, 500] # milliseconds
235
236
for target_ms in targets:
237
iterations = pbkdf2_iteration_calculator('sha256', 32, target_ms, quiet=True)
238
239
# Verify actual timing
240
start = time.time()
241
pbkdf2('sha256', b'test', b'salt1234', iterations, 32)
242
actual_ms = (time.time() - start) * 1000
243
244
print(f"Target: {target_ms}ms, Iterations: {iterations}, Actual: {actual_ms:.1f}ms")
245
```