code wiki / (root) / pbkdf2_sha1.nx

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}