code wiki / (root) / pbkdf2.nx

pbkdf2.nx source

↩ module page · 117 lines · 4386 B

1// pbkdf2.nx -- PBKDF2 password-based key derivation (RFC 8018). 2// 3// Stretches a user password (low-entropy) into a crypto key 4// (high-entropy) by iterating HMAC many times over (password, 5// salt, counter). The iteration count makes brute-force attacks 6// expensive while legitimate derivations pay only a one-time cost. 7// 8// Used for: user-auth password hashing, key wrapping, PKCS#5 9// v2 PBKDF2 (widely deployed in 1Password / KeePass / LastPass 10// container formats, TLS-PSK). 11// 12// Superseded for new systems by Argon2id (memory-hard; 13// harder to attack with GPUs). Still required for interop with 14// many existing databases + hardware tokens. 15// 16// Algorithm (RFC 8018 ยง5.2 PBKDF2): 17// For each `dkLen / hash_len` block: 18// U_1 = HMAC(password, salt || counter_as_u32_be) 19// U_i = HMAC(password, U_{i-1}) for i = 2..iterations 20// T_j = U_1 XOR U_2 XOR ... XOR U_iterations 21// Concatenate T_j for the final derived key. 22// 23// Invariants: 24// P1 iterations must be >= 1. OWASP-minimum for SHA-256 is 25// ~600_000 as of 2023. Caller chooses; we don't enforce. 26// P2 dkLen capped at (2^32 - 1) * hash_len. In practice nothing 27// needs more than a handful of blocks; we don't validate. 28// P3 Counter is 1-indexed, big-endian, appended to salt for the 29// U_1 computation. 30// P4 XOR accumulation is in-place to avoid allocating a per- 31// iteration scratch. 32 33import "syscalls.nx" 34import "nx_hmac.nx" // was hmac.nx -- CODE-IDENTICAL twin (49/49 stmts) on the LEGACY syscalls.nx+sha256.nx family. 35// Two files defining hmac_sha256 + main, with the expander deduping BY PATH NOT BY SYMBOL, made 36// every legacy importer a duplicate-symbol landmine for the nx_ family (debt 1785524913). 37 38const PBKDF2_HASH_LEN: i64 = 32 // HMAC-SHA-256 output size 39 40func pbkdf2_sha256(password: *u8, pass_len: i64, 41 salt: *u8, salt_len: i64, 42 iterations: i64, 43 dk_len: i64, 44 dk_out: *u8) -> i64 { 45 if iterations < 1 { return -1 } 46 if dk_len < 1 { return -1 } 47 48 // Number of blocks needed. 49 let l: i64 = (dk_len + PBKDF2_HASH_LEN - 1) / PBKDF2_HASH_LEN 50 51 // Scratch for the salt || counter input to the first HMAC. 52 let salt_ctr: *u8 = sys_mmap(salt_len + 4 + 16) 53 var i: i64 = 0 54 while i < salt_len { salt_ctr[i] = salt[i]; i = i + 1 } 55 56 let u_curr: *u8 = sys_mmap(PBKDF2_HASH_LEN) 57 let u_prev: *u8 = sys_mmap(PBKDF2_HASH_LEN) 58 let t: *u8 = sys_mmap(PBKDF2_HASH_LEN) 59 60 var block: i64 = 1 61 while block <= l { 62 // Big-endian u32 counter appended to salt. 63 salt_ctr[salt_len + 0] = (block >> 24) & 0xFF 64 salt_ctr[salt_len + 1] = (block >> 16) & 0xFF 65 salt_ctr[salt_len + 2] = (block >> 8) & 0xFF 66 salt_ctr[salt_len + 3] = block & 0xFF 67 68 // U_1 = HMAC(password, salt || counter). 69 hmac_sha256(password, pass_len, salt_ctr, salt_len + 4, u_curr) 70 // Initialise T_j = U_1. 71 var k: i64 = 0 72 while k < PBKDF2_HASH_LEN { 73 t[k] = u_curr[k] 74 u_prev[k] = u_curr[k] 75 k = k + 1 76 } 77 // U_2..U_iterations. 78 var iter: i64 = 2 79 while iter <= iterations { 80 hmac_sha256(password, pass_len, u_prev, PBKDF2_HASH_LEN, u_curr) 81 k = 0 82 while k < PBKDF2_HASH_LEN { 83 t[k] = t[k] ^ u_curr[k] 84 u_prev[k] = u_curr[k] 85 k = k + 1 86 } 87 iter = iter + 1 88 } 89 90 // Copy T_j into output at position (block-1) * HASH_LEN. 91 let out_off: i64 = (block - 1) * PBKDF2_HASH_LEN 92 k = 0 93 while k < PBKDF2_HASH_LEN { 94 let dk_pos: i64 = out_off + k 95 if dk_pos < dk_len { 96 dk_out[dk_pos] = t[k] 97 } 98 k = k + 1 99 } 100 block = block + 1 101 } 102 return 0 103} 104 105// Compile-only smoke. RFC 6070 vector 3: 106// password = "password", salt = "salt", iterations = 4096, 107// dkLen = 20 108// -> 4b007901b765489abead49d926f721d065a429c1 109func main() -> i64 { 110 let pw: *u8 = "password" 111 let salt: *u8 = "salt" 112 let dk: *u8 = sys_mmap(32) 113 // Use a LOW iteration count in smoke tests so compile stays 114 // fast; real callers use OWASP-minimum ~600000 for SHA-256. 115 pbkdf2_sha256(pw, 8, salt, 4, 4, 20, dk) 116 return dk[0] as i64 117}