pbkdf2_sha1.nx source
↩ module page · 115 lines · 3765 B
1// pbkdf2_sha1.nx -- PBKDF2 key derivation with HMAC-SHA1.
2//
3// RFC 2898 / PKCS#5 v2.0. The SHA-1-based variant was once the
4// web standard for password hashing and is still used by:
5// - WPA2 PSK -> PMK derivation (802.11i-2004 requires PBKDF2-SHA1
6// with 4096 iterations)
7// - Older OS X Keychain + iOS data protection
8// - 1Password .agilekeychain / .keychain vault decryption
9// - Legacy JWT \"PBES2\" JOSE key derivation
10//
11// For new password hashing use Argon2id (memory-hard) or at least
12// PBKDF2-SHA-256 with iter_count >= 600000 (OWASP 2023).
13//
14// Algorithm (ยง5.2):
15// DK = T_1 || T_2 || ... || T_l
16// T_i = F(password, salt, c, i)
17// F(P, S, c, i) = U_1 XOR U_2 XOR ... XOR U_c
18// U_1 = HMAC-SHA1(P, S || INT(i)) INT(i) is u32 big-endian
19// U_j = HMAC-SHA1(P, U_{j-1}) for j = 2..c
20//
21// Composes hmac_sha1.nx.
22//
23// Invariants:
24// PB1 Output length `dk_len` in bytes; at most 2^32 - 1 blocks
25// (not checked; callers requesting > 10MB keys are daft).
26// PB2 iterations must be >= 1; we treat 0 as \"no stretching\"
27// which RFC doesn't define.
28// PB3 Salt + password byte-exact; no UTF-8 normalisation.
29
30import "syscalls.nx"
31import "hmac_sha1.nx"
32
33const PB_HLEN: i64 = 20 // HMAC-SHA1 output size
34
35// Compute one output block T_i into `out[0..20]`.
36func pb_f_sha1(password: *u8, pass_len: i64,
37 salt: *u8, salt_len: i64,
38 iterations: i64, block_idx: i64,
39 out: *u8) -> i64 {
40 // U_1 = HMAC(P, S || INT(i))
41 let u_buf_len: i64 = salt_len + 4
42 let u_buf: *u8 = sys_mmap(u_buf_len + 16)
43 var i: i64 = 0
44 while i < salt_len {
45 u_buf[i] = salt[i]
46 i = i + 1
47 }
48 u_buf[salt_len] = (block_idx >> 24) & 0xFF
49 u_buf[salt_len + 1] = (block_idx >> 16) & 0xFF
50 u_buf[salt_len + 2] = (block_idx >> 8) & 0xFF
51 u_buf[salt_len + 3] = block_idx & 0xFF
52
53 let u: *u8 = sys_mmap(32)
54 hmac_sha1(password, pass_len, u_buf, u_buf_len, u)
55
56 // Accumulator starts as U_1.
57 i = 0
58 while i < PB_HLEN {
59 out[i] = u[i]
60 i = i + 1
61 }
62
63 // Iterate.
64 var j: i64 = 1
65 let u_next: *u8 = sys_mmap(32)
66 while j < iterations {
67 hmac_sha1(password, pass_len, u, PB_HLEN, u_next)
68 i = 0
69 while i < PB_HLEN {
70 u[i] = u_next[i]
71 out[i] = out[i] ^ u_next[i]
72 i = i + 1
73 }
74 j = j + 1
75 }
76 return 0
77}
78
79// Derive a key. Writes dk_len bytes to out.
80func pbkdf2_sha1(password: *u8, pass_len: i64,
81 salt: *u8, salt_len: i64,
82 iterations: i64,
83 out: *u8, dk_len: i64) -> i64 {
84 let block_buf: *u8 = sys_mmap(32)
85 var pos: i64 = 0
86 var block_idx: i64 = 1
87 while pos < dk_len {
88 pb_f_sha1(password, pass_len, salt, salt_len,
89 iterations, block_idx, block_buf)
90 var take: i64 = PB_HLEN
91 let remaining: i64 = dk_len - pos
92 if remaining < take { take = remaining }
93 var k: i64 = 0
94 while k < take {
95 out[pos + k] = block_buf[k]
96 k = k + 1
97 }
98 pos = pos + take
99 block_idx = block_idx + 1
100 }
101 return 0
102}
103
104// Compile-only smoke. RFC 6070 Test Vector 1:
105// P = "password", S = "salt", c = 1, dkLen = 20
106// DK = 0c60c80f961f0e71f3a9b524af6012062fe037a6
107func main() -> i64 {
108 let out: *u8 = sys_mmap(32)
109 pbkdf2_sha1("password", 8, "salt", 4, 1, out, 20)
110 if out[0] != 0x0C { return 1 }
111 if out[1] != 0x60 { return 2 }
112 if out[2] != 0xC8 { return 3 }
113 if out[19] != 0xA6 { return 4 }
114 return 0
115}