hmac_sha1.nx source
↩ module page · 121 lines · 4820 B
1// hmac_sha1.nx -- HMAC with SHA-1 (RFC 2104 + FIPS 198-1).
2//
3// Interop-only primitive, like sha1.nx itself. HMAC does not
4// rely on its inner hash's collision resistance (only pseudo-
5// randomness of the keyed mix), so HMAC-SHA1 remains safe for
6// authentication even though plain SHA-1 is broken for signatures.
7//
8// Still widely used:
9// - TOTP / HOTP (RFC 4226 / 6238 default)
10// - OAuth 1.0 HMAC-SHA1 signatures
11// - AWS SigV2 / legacy API auth
12// - PBKDF2-HMAC-SHA1 (WPA2, older WebCrypto key derivation)
13// - Older JWT HS1 tokens
14//
15// Algorithm (RFC 2104):
16// block_size = 64 bytes for SHA-1
17// if len(key) > block_size: key = SHA1(key)
18// key = key || zeros to block_size
19// ipad = key XOR 0x36 repeated
20// opad = key XOR 0x5C repeated
21// tag = SHA1(opad || SHA1(ipad || msg))
22//
23// Composes sha1.nx. Output is 20 bytes.
24//
25// Invariants:
26// HS1 Output = 20 bytes always (HMAC-SHA1 tag length).
27// HS2 Matches RFC 2202 test vectors (not checked in smoke,
28// but the algorithm is textbook).
29
30import "syscalls.nx"
31// 2026-08-01 -- REPOINTED FROM sha1.nx TO nx_sha1.nx. THIS ONE LINE IS THE sev-8 REMEDY.
32//
33// sha1.nx COMPUTES WRONG DIGESTS: sha1("abc") -> 35 04 .. against RFC 3174's published A9 99 3E 36.
34// Proven two ways, by two unrelated authorities:
35// RFC 3174 -- nx_sha1_extvec_gate (nx_sha1.nx) GREEN vs nx_sha1alt_extvec_gate (sha1.nx) RED
36// RFC 2202 -- 0/9 via sha1.nx vs 9/9 via nx_sha1.nx, changing ONLY the hash core
37// RFC 5869 -- nx_hkdf7_extvec_gate 7/7 vs nx_hkdf7alt_extvec_gate failing A.4-A.7, with the SHA-256
38// cases passing in BOTH arms as a control that must not move
39//
40// EVERY dependent reaches the core THROUGH THIS IMPORT, so repointing here fixes them all at once:
41// hkdf_sha1.nx · hotp_sha1.nx · totp_sha1.nx · pbkdf2_sha1.nx (and totp_verify.nx transitively)
42// i.e. HMAC-SHA-1, HKDF-SHA-1, HOTP, TOTP two-factor codes, and PBKDF2 password derivation were ALL
43// producing incorrect values. ★COUNT THE IMPORT CLOSURE, NOT THE FILES YOU HAPPENED TO GREP -- I filed
44// this defect twice with the wrong blast radius (3, then 5) before reading the closure and finding 6.
45//
46// SAFE AS A DROP-IN, VERIFIED BEFORE EDITING rather than assumed: nx_sha1.nx exports the SAME symbol set
47// (sha1, sha1_rotl, sha1_process_block, if_ge, SHA1_H0..K3, SHA1_MASK32) and is a strict SUPERSET (adds
48// sha1_into + its scratch constants). No caller signature changes. ★A DROP-IN REPLACEMENT IS A CLAIM ABOUT
49// SYMBOLS -- CHECK THE SYMBOL SETS, DO NOT INFER IT FROM THE FILENAMES BEING SIMILAR.
50// ⚠sha1.nx is left in the tree ON PURPOSE so nx_sha1alt_extvec_gate can keep convicting it; retiring it is
51// a separate, later step.
52import "nx_sha1.nx"
53
54const HMAC_SHA1_BLOCK: i64 = 64
55const HMAC_SHA1_OUT: i64 = 20
56
57func hmac_sha1(key: *u8, key_len: i64,
58 msg: *u8, msg_len: i64,
59 out: *u8) -> i64 {
60 let k_prime: *u8 = sys_mmap(HMAC_SHA1_BLOCK + 16)
61 var i: i64 = 0
62 while i < HMAC_SHA1_BLOCK { k_prime[i] = 0; i = i + 1 }
63
64 if key_len > HMAC_SHA1_BLOCK {
65 // Shorten long keys by hashing.
66 sha1(key, key_len, k_prime)
67 } else {
68 i = 0
69 while i < key_len {
70 k_prime[i] = key[i]
71 i = i + 1
72 }
73 }
74
75 // Inner hash: SHA1(ipad || msg).
76 let inner_buf_len: i64 = HMAC_SHA1_BLOCK + msg_len
77 let inner_buf: *u8 = sys_mmap(inner_buf_len + 16)
78 i = 0
79 while i < HMAC_SHA1_BLOCK {
80 inner_buf[i] = k_prime[i] ^ 0x36
81 i = i + 1
82 }
83 i = 0
84 while i < msg_len {
85 inner_buf[HMAC_SHA1_BLOCK + i] = msg[i]
86 i = i + 1
87 }
88 let inner_hash: *u8 = sys_mmap(32)
89 sha1(inner_buf, inner_buf_len, inner_hash)
90
91 // Outer hash: SHA1(opad || inner_hash).
92 let outer_buf_len: i64 = HMAC_SHA1_BLOCK + HMAC_SHA1_OUT
93 let outer_buf: *u8 = sys_mmap(outer_buf_len + 16)
94 i = 0
95 while i < HMAC_SHA1_BLOCK {
96 outer_buf[i] = k_prime[i] ^ 0x5C
97 i = i + 1
98 }
99 i = 0
100 while i < HMAC_SHA1_OUT {
101 outer_buf[HMAC_SHA1_BLOCK + i] = inner_hash[i]
102 i = i + 1
103 }
104 sha1(outer_buf, outer_buf_len, out)
105 return 0
106}
107
108// Compile-only smoke.
109func main() -> i64 {
110 let out: *u8 = sys_mmap(32)
111 hmac_sha1("key", 3, "The quick brown fox jumps over the lazy dog", 43, out)
112 // RFC 2202 test vector:
113 // HMAC-SHA1(key=\"key\", msg=\"The quick brown fox jumps over the lazy dog\")
114 // = de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9
115 if out[0] != 0xDE { return 1 }
116 if out[1] != 0x7C { return 2 }
117 if out[2] != 0x9B { return 3 }
118 if out[3] != 0x85 { return 4 }
119 if out[19] != 0xD9 { return 5 }
120 return 0
121}