code wiki / (root) / nx_pbkdf2_sha1.nx

nx_pbkdf2_sha1.nx source

↩ module page · 121 lines · 3907 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 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "nx_syscalls.nx" 37import "nx_hmac_sha1.nx" 38 39const PB_HLEN: i64 = 20 // HMAC-SHA1 output size 40 41// Compute one output block T_i into `out[0..20]`. 42func pb_f_sha1(password: *u8, pass_len: i64, 43 salt: *u8, salt_len: i64, 44 iterations: i64, block_idx: i64, 45 out: *u8) -> i64 { 46 // U_1 = HMAC(P, S || INT(i)) 47 let u_buf_len: i64 = salt_len + 4 48 let u_buf: *u8 = sys_mmap(u_buf_len + 16) 49 var i: i64 = 0 50 while i < salt_len { 51 u_buf[i] = salt[i] 52 i = i + 1 53 } 54 u_buf[salt_len] = (block_idx >> 24) & 0xFF 55 u_buf[salt_len + 1] = (block_idx >> 16) & 0xFF 56 u_buf[salt_len + 2] = (block_idx >> 8) & 0xFF 57 u_buf[salt_len + 3] = block_idx & 0xFF 58 59 let u: *u8 = sys_mmap(32) 60 hmac_sha1(password, pass_len, u_buf, u_buf_len, u) 61 62 // Accumulator starts as U_1. 63 i = 0 64 while i < PB_HLEN { 65 out[i] = u[i] 66 i = i + 1 67 } 68 69 // Iterate. 70 var j: i64 = 1 71 let u_next: *u8 = sys_mmap(32) 72 while j < iterations { 73 hmac_sha1(password, pass_len, u, PB_HLEN, u_next) 74 i = 0 75 while i < PB_HLEN { 76 u[i] = u_next[i] 77 out[i] = out[i] ^ u_next[i] 78 i = i + 1 79 } 80 j = j + 1 81 } 82 return 0 83} 84 85// Derive a key. Writes dk_len bytes to out. 86func pbkdf2_sha1(password: *u8, pass_len: i64, 87 salt: *u8, salt_len: i64, 88 iterations: i64, 89 out: *u8, dk_len: i64) -> i64 { 90 let block_buf: *u8 = sys_mmap(32) 91 var pos: i64 = 0 92 var block_idx: i64 = 1 93 while pos < dk_len { 94 pb_f_sha1(password, pass_len, salt, salt_len, 95 iterations, block_idx, block_buf) 96 var take: i64 = PB_HLEN 97 let remaining: i64 = dk_len - pos 98 if remaining < take { take = remaining } 99 var k: i64 = 0 100 while k < take { 101 out[pos + k] = block_buf[k] 102 k = k + 1 103 } 104 pos = pos + take 105 block_idx = block_idx + 1 106 } 107 return 0 108} 109 110// Compile-only smoke. RFC 6070 Test Vector 1: 111// P = "password", S = "salt", c = 1, dkLen = 20 112// DK = 0c60c80f961f0e71f3a9b524af6012062fe037a6 113func main() -> i64 { 114 let out: *u8 = sys_mmap(32) 115 pbkdf2_sha1("password", 8, "salt", 4, 1, out, 20) 116 if out[0] != 0x0C { return 1 } 117 if out[1] != 0x60 { return 2 } 118 if out[2] != 0xC8 { return 3 } 119 if out[19] != 0xA6 { return 4 } 120 return 0 121}