hmac_md5.nx source
↩ module page · 104 lines · 3563 B
1// hmac_md5.nx -- HMAC with MD5 (RFC 2104).
2//
3// Like HMAC-SHA1: even though plain MD5 is broken for signatures,
4// HMAC-MD5 remains safe for authentication because HMAC relies
5// on pseudo-randomness of the keyed mix, not collision resistance.
6// Still on the wire in:
7// - RADIUS Access-Request Message-Authenticator (RFC 3579)
8// - CRAM-MD5 SASL auth (IMAP, POP3, SMTP legacy)
9// - Microsoft MS-CHAP-v2 password challenge
10// - NTLMv1 / NTLMv2 (the real world's Windows auth)
11//
12// Composes md5.nx with standard ipad/opad construction.
13//
14// Invariants:
15// HM1 Output = 16 bytes (MD5 tag length).
16// HM2 Matches RFC 2104 Appendix test vectors.
17
18import "syscalls.nx"
19// 2026-08-01 -- REPOINTED FROM md5.nx TO nx_md5_canonical.nx. SECOND sev-8 OF THE SAME SHAPE AS sha1.nx.
20//
21// md5.nx COMPUTES WRONG DIGESTS. Adjudicated by RFC 2202 section 2 (7 published HMAC-MD5 vectors, in a
22// document already pinned and cross-stack corroborated):
23// nx_hmacmd5_extvec_gate -> nx_hmac_md5.nx -> nx_md5_canonical.nx 7/7 GREEN
24// nx_hmacmd5alt_extvec_gate -> hmac_md5.nx -> md5.nx 0/7 RED
25// One import apart, identical readers, identical document. * A DUPLICATE PAIR IS A COIN FLIP UNTIL AN
26// AUTHORITY ADJUDICATES IT -- and the un-prefixed twin lost, exactly as sha1.nx did.
27//
28// VERIFIED DROP-IN BEFORE EDITING: both files export the SAME symbol set
29// (md5, md5_rotl, md5_s, md5_k, md5_g, md5_process_block, MD5_MASK32) -- nothing is lost by repointing.
30// * A "DROP-IN REPLACEMENT" IS A CLAIM ABOUT SYMBOL SETS; CHECK THEM.
31import "nx_md5_canonical.nx"
32
33const HMAC_MD5_BLOCK: i64 = 64
34const HMAC_MD5_OUT: i64 = 16
35
36func hmac_md5(key: *u8, key_len: i64,
37 msg: *u8, msg_len: i64,
38 out: *u8) -> i64 {
39 let k_prime: *u8 = sys_mmap(HMAC_MD5_BLOCK + 16)
40 var i: i64 = 0
41 while i < HMAC_MD5_BLOCK { k_prime[i] = 0; i = i + 1 }
42
43 if key_len > HMAC_MD5_BLOCK {
44 md5(key, key_len, k_prime)
45 } else {
46 i = 0
47 while i < key_len {
48 k_prime[i] = key[i]
49 i = i + 1
50 }
51 }
52
53 // Inner: MD5(ipad || msg).
54 let inner_len: i64 = HMAC_MD5_BLOCK + msg_len
55 let inner_buf: *u8 = sys_mmap(inner_len + 16)
56 i = 0
57 while i < HMAC_MD5_BLOCK {
58 inner_buf[i] = k_prime[i] ^ 0x36
59 i = i + 1
60 }
61 i = 0
62 while i < msg_len {
63 inner_buf[HMAC_MD5_BLOCK + i] = msg[i]
64 i = i + 1
65 }
66 let inner_hash: *u8 = sys_mmap(32)
67 md5(inner_buf, inner_len, inner_hash)
68
69 // Outer: MD5(opad || inner_hash).
70 let outer_len: i64 = HMAC_MD5_BLOCK + HMAC_MD5_OUT
71 let outer_buf: *u8 = sys_mmap(outer_len + 16)
72 i = 0
73 while i < HMAC_MD5_BLOCK {
74 outer_buf[i] = k_prime[i] ^ 0x5C
75 i = i + 1
76 }
77 i = 0
78 while i < HMAC_MD5_OUT {
79 outer_buf[HMAC_MD5_BLOCK + i] = inner_hash[i]
80 i = i + 1
81 }
82 md5(outer_buf, outer_len, out)
83 return 0
84}
85
86// Compile-only smoke.
87func main() -> i64 {
88 let out: *u8 = sys_mmap(32)
89 // RFC 2104 test vector 1:
90 // key = 0x0b repeated 16 times, msg = "Hi There"
91 // HMAC-MD5 = 9294727a3638bb1c13f48ef8158bfc9d
92 let key: *u8 = sys_mmap(32)
93 var i: i64 = 0
94 while i < 16 {
95 key[i] = 0x0B
96 i = i + 1
97 }
98 hmac_md5(key, 16, "Hi There", 8, out)
99 if out[0] != 0x92 { return 1 }
100 if out[1] != 0x94 { return 2 }
101 if out[2] != 0x72 { return 3 }
102 if out[15] != 0x9D { return 4 }
103 return 0
104}