code wiki / (root) / nx_drbg_hmac.nx

nx_drbg_hmac.nx source

↩ module page · 235 lines · 8232 B

1// nx_drbg_hmac.nx -- NIST SP 800-90A HMAC-DRBG with SHA-256. 2// 3// license_tier: INDEPENDENT_REDERIVE 4// genealogy_id: international-research-sources/nist/sp_800_90a 5// 6// Deterministic Random Bit Generator. Given a seed (entropy + 7// optional personalization), produces an arbitrary-length pseudorandom 8// stream that any party with the same seed can reproduce. Built on 9// HMAC-SHA-256 per SP 800-90A §10.1.2. 10// 11// Why this exists alongside nx_csprng: 12// - nx_csprng reads /dev/urandom (getrandom syscall). Online-only, 13// no determinism, useless on MCU/sensor targets without an OS 14// entropy source. 15// - nx_drbg_hmac takes a seed and produces a deterministic stream. 16// Replayable for testing; usable in air-gapped + embedded contexts; 17// foundation for NIST post-quantum signing (ML-DSA, SLH-DSA). 18// 19// State layout (72 bytes, caller-allocated): 20// bytes 0..31 : K (HMAC key, output_len = 32 bytes) 21// bytes 32..63 : V (current value, output_len = 32 bytes) 22// bytes 64..71 : reseed_counter (i64) 23// 24// SP 800-90A §10.1.2.2 Update operation: 25// K = HMAC(K, V || 0x00 || data) 26// V = HMAC(K, V) 27// if len(data) > 0: 28// K = HMAC(K, V || 0x01 || data) 29// V = HMAC(K, V) 30// 31// §10.1.2.3 Instantiate: 32// K = 0x00 * 32 ; V = 0x01 * 32 33// Update(seed_material) 34// reseed_counter = 1 35// 36// §10.1.2.5 Generate: 37// temp = empty 38// while len(temp) < requested_bytes: 39// V = HMAC(K, V) 40// temp ||= V 41// Update(empty) -- even with no additional input 42// reseed_counter += 1 43// 44// Invariants: 45// D1 State buffer is exactly 72 bytes. Caller allocates; this 46// module never frees -- sys_mmap pages live until process exit. 47// D2 Output length per call <= 65536 (2^19 / 8) per SP 800-90A. 48// Caller wanting more must loop with reseed in between. 49// D3 Reseed_counter increments on every Generate call; never 50// reaches the 2^48 SP 800-90A maximum in practice. 51// D4 HMAC-SHA-256 is constant-time on its inputs; DRBG state 52// transitions are similarly constant-time on V and K. 53// 54// nx_safety_envelope: 55// intended_use: "HMAC-based deterministic random bit generator 56// (NIST SP 800-90A §10.1.2) -- CSPRNG output 57// for key gen / nonce derivation / signature 58// scalars" 59// sil_target: SIL3 (RNG primitive; weak output = 60// broken keys = arbitrary system 61// compromise) 62// asil_target: QM 63// dal_target: DAL B 64// iec_62304_class: B 65// evidence: [NIST_SP_800-90A_canonical_basis, no_FP, 66// constant_time_V_K_transitions, 67// backtracking_resistance_via_K_reseed, 68// CAVS_test_vectors_target] 69// hazard_register: [bug-tape-DRBG-low-entropy-init, 70// bug-tape-reseed-counter-overflow, 71// bug-tape-state-not-cleared-after-use] 72// residual_risk: "Initial seed entropy is CALLER 73// RESPONSIBILITY. Substrate provides the 74// DRBG transform but cannot enforce that 75// the seed has ≥256 bits of entropy." 76// verdict: NOT_YET_EVALUATED 77 78import "nx_syscalls.nx" 79import "nx_sha256.nx" 80import "nx_hmac.nx" 81 82const DRBG_OUTLEN: i64 = 32 // HMAC-SHA-256 output bytes 83const DRBG_STATE_LEN: i64 = 72 // K(32) + V(32) + reseed_counter(8) 84const DRBG_K_OFF: i64 = 0 85const DRBG_V_OFF: i64 = 32 86const DRBG_RC_OFF: i64 = 64 87const DRBG_MAX_BYTES: i64 = 65536 // per SP 800-90A §10.1 table 88 89// ---- internal helpers ---------------------------------------------- 90 91func _drbg_k(state: *u8) -> *u8 { 92 return (state as i64 + DRBG_K_OFF) as *u8 93} 94 95func _drbg_v(state: *u8) -> *u8 { 96 return (state as i64 + DRBG_V_OFF) as *u8 97} 98 99// Copy n bytes from src to dst (no overlap assumed; DRBG callers 100// always allocate fresh buffers). 101func _drbg_copy(dst: *u8, src: *u8, n: i64) -> i64 { 102 var i: i64 = 0 103 while i < n { dst[i] = src[i]; i = i + 1 } 104 return 0 105} 106 107func _drbg_fill(dst: *u8, val: i64, n: i64) -> i64 { 108 var i: i64 = 0 109 while i < n { dst[i] = val; i = i + 1 } 110 return 0 111} 112 113// ---- Update (SP 800-90A §10.1.2.2) --------------------------------- 114// 115// K = HMAC(K, V || sep || data) 116// V = HMAC(K, V) 117// if data_len > 0: 118// K = HMAC(K, V || 0x01 || data) 119// V = HMAC(K, V) 120// 121// We build the HMAC message in a scratch buffer of length 122// 32 + 1 + data_len. 123 124func drbg_hmac_update(state: *u8, data: *u8, data_len: i64) -> i64 { 125 let k: *u8 = _drbg_k(state) 126 let v: *u8 = _drbg_v(state) 127 128 // First pass: separator byte 0x00. 129 let msg_len: i64 = DRBG_OUTLEN + 1 + data_len 130 let msg: *u8 = sys_mmap(msg_len + 32) 131 _drbg_copy(msg, v, DRBG_OUTLEN) 132 msg[DRBG_OUTLEN] = 0x00 133 if data_len > 0 { 134 let dst: *u8 = (msg as i64 + DRBG_OUTLEN + 1) as *u8 135 _drbg_copy(dst, data, data_len) 136 } 137 let new_k: *u8 = sys_mmap(DRBG_OUTLEN + 16) 138 hmac_sha256(k, DRBG_OUTLEN, msg, msg_len, new_k) 139 _drbg_copy(k, new_k, DRBG_OUTLEN) 140 141 // V = HMAC(K, V) 142 let new_v: *u8 = sys_mmap(DRBG_OUTLEN + 16) 143 hmac_sha256(k, DRBG_OUTLEN, v, DRBG_OUTLEN, new_v) 144 _drbg_copy(v, new_v, DRBG_OUTLEN) 145 146 if data_len == 0 { return 0 } 147 148 // Second pass: separator byte 0x01. 149 let msg2_len: i64 = DRBG_OUTLEN + 1 + data_len 150 let msg2: *u8 = sys_mmap(msg2_len + 32) 151 _drbg_copy(msg2, v, DRBG_OUTLEN) 152 msg2[DRBG_OUTLEN] = 0x01 153 let dst2: *u8 = (msg2 as i64 + DRBG_OUTLEN + 1) as *u8 154 _drbg_copy(dst2, data, data_len) 155 156 let new_k2: *u8 = sys_mmap(DRBG_OUTLEN + 16) 157 hmac_sha256(k, DRBG_OUTLEN, msg2, msg2_len, new_k2) 158 _drbg_copy(k, new_k2, DRBG_OUTLEN) 159 160 let new_v2: *u8 = sys_mmap(DRBG_OUTLEN + 16) 161 hmac_sha256(k, DRBG_OUTLEN, v, DRBG_OUTLEN, new_v2) 162 _drbg_copy(v, new_v2, DRBG_OUTLEN) 163 return 0 164} 165 166// ---- Instantiate (SP 800-90A §10.1.2.3) --------------------------- 167// 168// K = 0x00 * outlen 169// V = 0x01 * outlen 170// Update(seed_material) 171// reseed_counter = 1 172// 173// `state` must point to >= 72 bytes of writable memory. 174 175func drbg_hmac_init(state: *u8, seed: *u8, seed_len: i64) -> i64 { 176 let k: *u8 = _drbg_k(state) 177 let v: *u8 = _drbg_v(state) 178 _drbg_fill(k, 0x00, DRBG_OUTLEN) 179 _drbg_fill(v, 0x01, DRBG_OUTLEN) 180 drbg_hmac_update(state, seed, seed_len) 181 let rc_p: *i64 = (state as i64 + DRBG_RC_OFF) as *i64 182 rc_p[0] = 1 183 return 0 184} 185 186// ---- Generate (SP 800-90A §10.1.2.5) ------------------------------- 187// 188// temp = empty 189// while len(temp) < n_bytes: 190// V = HMAC(K, V) 191// temp ||= V 192// Update(additional_input) -- substrate variant: no additional input 193// reseed_counter += 1 194 195func drbg_hmac_generate(state: *u8, out: *u8, n_bytes: i64) -> i64 { 196 if n_bytes <= 0 { return 0 } 197 if n_bytes > DRBG_MAX_BYTES { return -1 } 198 let k: *u8 = _drbg_k(state) 199 let v: *u8 = _drbg_v(state) 200 let new_v: *u8 = sys_mmap(DRBG_OUTLEN + 16) 201 var produced: i64 = 0 202 while produced < n_bytes { 203 hmac_sha256(k, DRBG_OUTLEN, v, DRBG_OUTLEN, new_v) 204 _drbg_copy(v, new_v, DRBG_OUTLEN) 205 var i: i64 = 0 206 let remaining: i64 = n_bytes - produced 207 let take: i64 = DRBG_OUTLEN 208 if remaining < take { 209 let take2: i64 = remaining 210 while i < take2 { 211 let dst_ptr: *u8 = (out as i64 + produced + i) as *u8 212 dst_ptr[0] = v[i] 213 i = i + 1 214 } 215 produced = produced + take2 216 } else { 217 while i < take { 218 let dst_ptr: *u8 = (out as i64 + produced + i) as *u8 219 dst_ptr[0] = v[i] 220 i = i + 1 221 } 222 produced = produced + take 223 } 224 } 225 // SP 800-90A: even with no additional input, finalize state. 226 drbg_hmac_update(state, 0 as *u8, 0) 227 let rc_p: *i64 = (state as i64 + DRBG_RC_OFF) as *i64 228 rc_p[0] = rc_p[0] + 1 229 return n_bytes 230} 231 232func drbg_hmac_reseed_counter(state: *u8) -> i64 { 233 let rc_p: *i64 = (state as i64 + DRBG_RC_OFF) as *i64 234 return rc_p[0] 235}