nx_md5.nx source
↩ module page · 246 lines · 9362 B
1// nx_md5.nx -- MD5 message digest (RFC 1321).
2//
3// MD5 is cryptographically broken (collisions) but still used for:
4// - Legacy file checksums (md5sum verification)
5// - Apache .htdigest auth files (which we may need to interop)
6// - ETag generation (when collision resistance isn't needed)
7// - Cache keys for systems that hardcode 16-byte digests
8//
9// DO NOT use MD5 for passwords, signatures, or anything where an
10// attacker controls the input. Use SHA-256 (already in tree) +
11// HMAC.
12//
13// Algorithm: 64-round MD-construction with 4-word state, processing
14// 64-byte blocks. Returns 16-byte digest.
15
16// nx_safety_envelope:
17// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
18// sil_target: SIL1
19// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
20// verdict: NOT_YET_EVALUATED
21
22import "syscalls.nx"
23
24// 64-bit rotate-left for 32-bit values (MD5 ops are 32-bit).
25func nx_md5_rotl(x: i64, k: i64) -> i64 {
26 let v: i64 = x & 0xFFFFFFFF
27 let lo: i64 = (v << k) & 0xFFFFFFFF
28 let hi: i64 = (v >> (32 - k)) & ((1 << (32 - k)) - 1)
29 return (lo | hi) & 0xFFFFFFFF
30}
31
32// MD5 round functions. Each operates on 32-bit words.
33func nx_md5_F(x: i64, y: i64, z: i64) -> i64 { return ((x & y) | ((~x) & z)) & 0xFFFFFFFF }
34func nx_md5_G(x: i64, y: i64, z: i64) -> i64 { return ((x & z) | (y & (~z))) & 0xFFFFFFFF }
35func nx_md5_H(x: i64, y: i64, z: i64) -> i64 { return (x ^ y ^ z) & 0xFFFFFFFF }
36func nx_md5_I(x: i64, y: i64, z: i64) -> i64 { return (y ^ (x | (~z))) & 0xFFFFFFFF }
37
38// FF/GG/HH/II step macros.
39func nx_md5_FF(a: i64, b: i64, c: i64, d: i64, m: i64, s: i64, t: i64) -> i64 {
40 let v: i64 = (a + nx_md5_F(b, c, d) + m + t) & 0xFFFFFFFF
41 return (b + nx_md5_rotl(v, s)) & 0xFFFFFFFF
42}
43func nx_md5_GG(a: i64, b: i64, c: i64, d: i64, m: i64, s: i64, t: i64) -> i64 {
44 let v: i64 = (a + nx_md5_G(b, c, d) + m + t) & 0xFFFFFFFF
45 return (b + nx_md5_rotl(v, s)) & 0xFFFFFFFF
46}
47func nx_md5_HH(a: i64, b: i64, c: i64, d: i64, m: i64, s: i64, t: i64) -> i64 {
48 let v: i64 = (a + nx_md5_H(b, c, d) + m + t) & 0xFFFFFFFF
49 return (b + nx_md5_rotl(v, s)) & 0xFFFFFFFF
50}
51func nx_md5_II(a: i64, b: i64, c: i64, d: i64, m: i64, s: i64, t: i64) -> i64 {
52 let v: i64 = (a + nx_md5_I(b, c, d) + m + t) & 0xFFFFFFFF
53 return (b + nx_md5_rotl(v, s)) & 0xFFFFFFFF
54}
55
56// Read u32 LE from byte buffer.
57func nx_md5_load_u32(p: *u8) -> i64 {
58 return (p[0] as i64)
59 | ((p[1] as i64) << 8)
60 | ((p[2] as i64) << 16)
61 | ((p[3] as i64) << 24)
62}
63
64// Process one 64-byte block, updating state[0..4].
65func nx_md5_block(state: *i64, blk: *u8) -> i64 {
66 var a: i64 = state[0]
67 var b: i64 = state[1]
68 var c: i64 = state[2]
69 var d: i64 = state[3]
70
71 // Load 16 message words (LE).
72 let m_raw: *u8 = sys_mmap(64)
73 let m: *i64 = m_raw as *i64
74 var i: i64 = 0
75 while i < 16 {
76 let p: *u8 = (((blk as i64) + i * 4) as *u8)
77 m[i] = nx_md5_load_u32(p)
78 i = i + 1
79 }
80
81 // Round 1
82 a = nx_md5_FF(a, b, c, d, m[0], 7, 0xD76AA478)
83 d = nx_md5_FF(d, a, b, c, m[1], 12, 0xE8C7B756)
84 c = nx_md5_FF(c, d, a, b, m[2], 17, 0x242070DB)
85 b = nx_md5_FF(b, c, d, a, m[3], 22, 0xC1BDCEEE)
86 a = nx_md5_FF(a, b, c, d, m[4], 7, 0xF57C0FAF)
87 d = nx_md5_FF(d, a, b, c, m[5], 12, 0x4787C62A)
88 c = nx_md5_FF(c, d, a, b, m[6], 17, 0xA8304613)
89 b = nx_md5_FF(b, c, d, a, m[7], 22, 0xFD469501)
90 a = nx_md5_FF(a, b, c, d, m[8], 7, 0x698098D8)
91 d = nx_md5_FF(d, a, b, c, m[9], 12, 0x8B44F7AF)
92 c = nx_md5_FF(c, d, a, b, m[10], 17, 0xFFFF5BB1)
93 b = nx_md5_FF(b, c, d, a, m[11], 22, 0x895CD7BE)
94 a = nx_md5_FF(a, b, c, d, m[12], 7, 0x6B901122)
95 d = nx_md5_FF(d, a, b, c, m[13], 12, 0xFD987193)
96 c = nx_md5_FF(c, d, a, b, m[14], 17, 0xA679438E)
97 b = nx_md5_FF(b, c, d, a, m[15], 22, 0x49B40821)
98
99 // Round 2
100 a = nx_md5_GG(a, b, c, d, m[1], 5, 0xF61E2562)
101 d = nx_md5_GG(d, a, b, c, m[6], 9, 0xC040B340)
102 c = nx_md5_GG(c, d, a, b, m[11], 14, 0x265E5A51)
103 b = nx_md5_GG(b, c, d, a, m[0], 20, 0xE9B6C7AA)
104 a = nx_md5_GG(a, b, c, d, m[5], 5, 0xD62F105D)
105 d = nx_md5_GG(d, a, b, c, m[10], 9, 0x02441453)
106 c = nx_md5_GG(c, d, a, b, m[15], 14, 0xD8A1E681)
107 b = nx_md5_GG(b, c, d, a, m[4], 20, 0xE7D3FBC8)
108 a = nx_md5_GG(a, b, c, d, m[9], 5, 0x21E1CDE6)
109 d = nx_md5_GG(d, a, b, c, m[14], 9, 0xC33707D6)
110 c = nx_md5_GG(c, d, a, b, m[3], 14, 0xF4D50D87)
111 b = nx_md5_GG(b, c, d, a, m[8], 20, 0x455A14ED)
112 a = nx_md5_GG(a, b, c, d, m[13], 5, 0xA9E3E905)
113 d = nx_md5_GG(d, a, b, c, m[2], 9, 0xFCEFA3F8)
114 c = nx_md5_GG(c, d, a, b, m[7], 14, 0x676F02D9)
115 b = nx_md5_GG(b, c, d, a, m[12], 20, 0x8D2A4C8A)
116
117 // Round 3
118 a = nx_md5_HH(a, b, c, d, m[5], 4, 0xFFFA3942)
119 d = nx_md5_HH(d, a, b, c, m[8], 11, 0x8771F681)
120 c = nx_md5_HH(c, d, a, b, m[11], 16, 0x6D9D6122)
121 b = nx_md5_HH(b, c, d, a, m[14], 23, 0xFDE5380C)
122 a = nx_md5_HH(a, b, c, d, m[1], 4, 0xA4BEEA44)
123 d = nx_md5_HH(d, a, b, c, m[4], 11, 0x4BDECFA9)
124 c = nx_md5_HH(c, d, a, b, m[7], 16, 0xF6BB4B60)
125 b = nx_md5_HH(b, c, d, a, m[10], 23, 0xBEBFBC70)
126 a = nx_md5_HH(a, b, c, d, m[13], 4, 0x289B7EC6)
127 d = nx_md5_HH(d, a, b, c, m[0], 11, 0xEAA127FA)
128 c = nx_md5_HH(c, d, a, b, m[3], 16, 0xD4EF3085)
129 b = nx_md5_HH(b, c, d, a, m[6], 23, 0x04881D05)
130 a = nx_md5_HH(a, b, c, d, m[9], 4, 0xD9D4D039)
131 d = nx_md5_HH(d, a, b, c, m[12], 11, 0xE6DB99E5)
132 c = nx_md5_HH(c, d, a, b, m[15], 16, 0x1FA27CF8)
133 b = nx_md5_HH(b, c, d, a, m[2], 23, 0xC4AC5665)
134
135 // Round 4
136 a = nx_md5_II(a, b, c, d, m[0], 6, 0xF4292244)
137 d = nx_md5_II(d, a, b, c, m[7], 10, 0x432AFF97)
138 c = nx_md5_II(c, d, a, b, m[14], 15, 0xAB9423A7)
139 b = nx_md5_II(b, c, d, a, m[5], 21, 0xFC93A039)
140 a = nx_md5_II(a, b, c, d, m[12], 6, 0x655B59C3)
141 d = nx_md5_II(d, a, b, c, m[3], 10, 0x8F0CCC92)
142 c = nx_md5_II(c, d, a, b, m[10], 15, 0xFFEFF47D)
143 b = nx_md5_II(b, c, d, a, m[1], 21, 0x85845DD1)
144 a = nx_md5_II(a, b, c, d, m[8], 6, 0x6FA87E4F)
145 d = nx_md5_II(d, a, b, c, m[15], 10, 0xFE2CE6E0)
146 c = nx_md5_II(c, d, a, b, m[6], 15, 0xA3014314)
147 b = nx_md5_II(b, c, d, a, m[13], 21, 0x4E0811A1)
148 a = nx_md5_II(a, b, c, d, m[4], 6, 0xF7537E82)
149 d = nx_md5_II(d, a, b, c, m[11], 10, 0xBD3AF235)
150 c = nx_md5_II(c, d, a, b, m[2], 15, 0x2AD7D2BB)
151 b = nx_md5_II(b, c, d, a, m[9], 21, 0xEB86D391)
152
153 state[0] = (state[0] + a) & 0xFFFFFFFF
154 state[1] = (state[1] + b) & 0xFFFFFFFF
155 state[2] = (state[2] + c) & 0xFFFFFFFF
156 state[3] = (state[3] + d) & 0xFFFFFFFF
157 return 0
158}
159
160// Compute MD5 of `n` bytes from `data` into 16-byte `digest`.
161func nx_md5(data: *u8, n: i64, digest: *u8) -> i64 {
162 let state_raw: *u8 = sys_mmap(32)
163 let state: *i64 = state_raw as *i64
164 state[0] = 0x67452301
165 state[1] = 0xEFCDAB89
166 state[2] = 0x98BADCFE
167 state[3] = 0x10325476
168
169 var off: i64 = 0
170 while off + 64 <= n {
171 let p: *u8 = (((data as i64) + off) as *u8)
172 nx_md5_block(state, p)
173 off = off + 64
174 }
175
176 // Padding: 0x80 byte, then zeros, then length in bits as 8-byte LE.
177 let pad: *u8 = sys_mmap(128)
178 var pad_len: i64 = 0
179 let tail: i64 = n - off
180 var i: i64 = 0
181 while i < tail {
182 pad[i] = data[off + i]
183 i = i + 1
184 }
185 pad[tail] = 0x80
186 pad_len = tail + 1
187 // Pad with zeros until we land at length-mod-64 == 56.
188 var pad_done: i64 = 0
189 while pad_done == 0 {
190 let mod: i64 = pad_len & 63
191 if mod == 56 { pad_done = 1 }
192 else { pad[pad_len] = 0; pad_len = pad_len + 1 }
193 }
194 // Append 64-bit bit-length LE.
195 let bits: i64 = n * 8
196 pad[pad_len + 0] = bits & 0xFF
197 pad[pad_len + 1] = (bits >> 8) & 0xFF
198 pad[pad_len + 2] = (bits >> 16) & 0xFF
199 pad[pad_len + 3] = (bits >> 24) & 0xFF
200 pad[pad_len + 4] = (bits >> 32) & 0xFF
201 pad[pad_len + 5] = (bits >> 40) & 0xFF
202 pad[pad_len + 6] = (bits >> 48) & 0xFF
203 pad[pad_len + 7] = (bits >> 56) & 0xFF
204 pad_len = pad_len + 8
205
206 var po: i64 = 0
207 while po < pad_len {
208 let p: *u8 = (((pad as i64) + po) as *u8)
209 nx_md5_block(state, p)
210 po = po + 64
211 }
212
213 // Output digest LE.
214 var k: i64 = 0
215 while k < 4 {
216 digest[k * 4 + 0] = state[k] & 0xFF
217 digest[k * 4 + 1] = (state[k] >> 8) & 0xFF
218 digest[k * 4 + 2] = (state[k] >> 16) & 0xFF
219 digest[k * 4 + 3] = (state[k] >> 24) & 0xFF
220 k = k + 1
221 }
222 return 0
223}
224
225// ---- self-test ---------------------------------------------------
226
227func main() -> i64 {
228 let digest: *u8 = sys_mmap(16)
229
230 // MD5("") = D41D8CD98F00B204E9800998ECF8427E
231 let empty: *u8 = sys_mmap(8)
232 nx_md5(empty, 0, digest)
233 if digest[0] != 0xD4 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
234 if digest[1] != 0x1D { return __syscall(93, 2, 0, 0, 0, 0, 0) }
235 if digest[15] != 0x7E { return __syscall(93, 3, 0, 0, 0, 0, 0) }
236
237 // MD5("abc") = 900150983CD24FB0D6963F7D28E17F72
238 let abc: *u8 = sys_mmap(8)
239 abc[0] = 0x61; abc[1] = 0x62; abc[2] = 0x63
240 nx_md5(abc, 3, digest)
241 if digest[0] != 0x90 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
242 if digest[1] != 0x01 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
243 if digest[15] != 0x72 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
244
245 return 0
246}