master
1const std = @import("../std.zig");
2const mem = std.mem;
3const math = std.math;
4
5const RoundParam = struct {
6 a: usize,
7 b: usize,
8 c: usize,
9 d: usize,
10 k: usize,
11 s: u32,
12 t: u32,
13};
14
15fn roundParam(a: usize, b: usize, c: usize, d: usize, k: usize, s: u32, t: u32) RoundParam {
16 return RoundParam{
17 .a = a,
18 .b = b,
19 .c = c,
20 .d = d,
21 .k = k,
22 .s = s,
23 .t = t,
24 };
25}
26
27/// The MD5 function is now considered cryptographically broken.
28/// Namely, it is trivial to find multiple inputs producing the same hash.
29/// For a fast-performing, cryptographically secure hash function, see SHA512/256, BLAKE2 or BLAKE3.
30pub const Md5 = struct {
31 const Self = @This();
32 pub const block_length = 64;
33 pub const digest_length = 16;
34 pub const Options = struct {};
35
36 s: [4]u32,
37 // Streaming Cache
38 buf: [64]u8,
39 buf_len: u8,
40 total_len: u64,
41
42 pub fn init(options: Options) Self {
43 _ = options;
44 return Self{
45 .s = [_]u32{
46 0x67452301,
47 0xEFCDAB89,
48 0x98BADCFE,
49 0x10325476,
50 },
51 .buf = undefined,
52 .buf_len = 0,
53 .total_len = 0,
54 };
55 }
56
57 pub fn hash(data: []const u8, out: *[digest_length]u8, options: Options) void {
58 var d = Md5.init(options);
59 d.update(data);
60 d.final(out);
61 }
62
63 pub fn hashResult(data: []const u8) [digest_length]u8 {
64 var out: [digest_length]u8 = undefined;
65 var d = Md5.init(.{});
66 d.update(data);
67 d.final(&out);
68 return out;
69 }
70
71 pub fn update(d: *Self, b: []const u8) void {
72 var off: usize = 0;
73
74 // Partial buffer exists from previous update. Copy into buffer then hash.
75 if (d.buf_len != 0 and d.buf_len + b.len >= 64) {
76 off += 64 - d.buf_len;
77 @memcpy(d.buf[d.buf_len..][0..off], b[0..off]);
78
79 d.round(&d.buf);
80 d.buf_len = 0;
81 }
82
83 // Full middle blocks.
84 while (off + 64 <= b.len) : (off += 64) {
85 d.round(b[off..][0..64]);
86 }
87
88 // Copy any remainder for next pass.
89 const b_slice = b[off..];
90 @memcpy(d.buf[d.buf_len..][0..b_slice.len], b_slice);
91 d.buf_len += @as(u8, @intCast(b_slice.len));
92
93 // Md5 uses the bottom 64-bits for length padding
94 d.total_len +%= b.len;
95 }
96
97 pub fn final(d: *Self, out: *[digest_length]u8) void {
98 // The buffer here will never be completely full.
99 @memset(d.buf[d.buf_len..], 0);
100
101 // Append padding bits.
102 d.buf[d.buf_len] = 0x80;
103 d.buf_len += 1;
104
105 // > 448 mod 512 so need to add an extra round to wrap around.
106 if (64 - d.buf_len < 8) {
107 d.round(d.buf[0..]);
108 @memset(d.buf[0..], 0);
109 }
110
111 // Append message length.
112 var i: usize = 1;
113 var len = d.total_len >> 5;
114 d.buf[56] = @as(u8, @intCast(d.total_len & 0x1f)) << 3;
115 while (i < 8) : (i += 1) {
116 d.buf[56 + i] = @as(u8, @intCast(len & 0xff));
117 len >>= 8;
118 }
119
120 d.round(d.buf[0..]);
121
122 for (d.s, 0..) |s, j| {
123 mem.writeInt(u32, out[4 * j ..][0..4], s, .little);
124 }
125 }
126
127 fn round(d: *Self, b: *const [64]u8) void {
128 var s: [16]u32 = undefined;
129
130 var i: usize = 0;
131 while (i < 16) : (i += 1) {
132 s[i] = mem.readInt(u32, b[i * 4 ..][0..4], .little);
133 }
134
135 var v: [4]u32 = [_]u32{
136 d.s[0],
137 d.s[1],
138 d.s[2],
139 d.s[3],
140 };
141
142 const round0 = comptime [_]RoundParam{
143 roundParam(0, 1, 2, 3, 0, 7, 0xD76AA478),
144 roundParam(3, 0, 1, 2, 1, 12, 0xE8C7B756),
145 roundParam(2, 3, 0, 1, 2, 17, 0x242070DB),
146 roundParam(1, 2, 3, 0, 3, 22, 0xC1BDCEEE),
147 roundParam(0, 1, 2, 3, 4, 7, 0xF57C0FAF),
148 roundParam(3, 0, 1, 2, 5, 12, 0x4787C62A),
149 roundParam(2, 3, 0, 1, 6, 17, 0xA8304613),
150 roundParam(1, 2, 3, 0, 7, 22, 0xFD469501),
151 roundParam(0, 1, 2, 3, 8, 7, 0x698098D8),
152 roundParam(3, 0, 1, 2, 9, 12, 0x8B44F7AF),
153 roundParam(2, 3, 0, 1, 10, 17, 0xFFFF5BB1),
154 roundParam(1, 2, 3, 0, 11, 22, 0x895CD7BE),
155 roundParam(0, 1, 2, 3, 12, 7, 0x6B901122),
156 roundParam(3, 0, 1, 2, 13, 12, 0xFD987193),
157 roundParam(2, 3, 0, 1, 14, 17, 0xA679438E),
158 roundParam(1, 2, 3, 0, 15, 22, 0x49B40821),
159 };
160 inline for (round0) |r| {
161 v[r.a] = v[r.a] +% (v[r.d] ^ (v[r.b] & (v[r.c] ^ v[r.d]))) +% r.t +% s[r.k];
162 v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
163 }
164
165 const round1 = comptime [_]RoundParam{
166 roundParam(0, 1, 2, 3, 1, 5, 0xF61E2562),
167 roundParam(3, 0, 1, 2, 6, 9, 0xC040B340),
168 roundParam(2, 3, 0, 1, 11, 14, 0x265E5A51),
169 roundParam(1, 2, 3, 0, 0, 20, 0xE9B6C7AA),
170 roundParam(0, 1, 2, 3, 5, 5, 0xD62F105D),
171 roundParam(3, 0, 1, 2, 10, 9, 0x02441453),
172 roundParam(2, 3, 0, 1, 15, 14, 0xD8A1E681),
173 roundParam(1, 2, 3, 0, 4, 20, 0xE7D3FBC8),
174 roundParam(0, 1, 2, 3, 9, 5, 0x21E1CDE6),
175 roundParam(3, 0, 1, 2, 14, 9, 0xC33707D6),
176 roundParam(2, 3, 0, 1, 3, 14, 0xF4D50D87),
177 roundParam(1, 2, 3, 0, 8, 20, 0x455A14ED),
178 roundParam(0, 1, 2, 3, 13, 5, 0xA9E3E905),
179 roundParam(3, 0, 1, 2, 2, 9, 0xFCEFA3F8),
180 roundParam(2, 3, 0, 1, 7, 14, 0x676F02D9),
181 roundParam(1, 2, 3, 0, 12, 20, 0x8D2A4C8A),
182 };
183 inline for (round1) |r| {
184 v[r.a] = v[r.a] +% (v[r.c] ^ (v[r.d] & (v[r.b] ^ v[r.c]))) +% r.t +% s[r.k];
185 v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
186 }
187
188 const round2 = comptime [_]RoundParam{
189 roundParam(0, 1, 2, 3, 5, 4, 0xFFFA3942),
190 roundParam(3, 0, 1, 2, 8, 11, 0x8771F681),
191 roundParam(2, 3, 0, 1, 11, 16, 0x6D9D6122),
192 roundParam(1, 2, 3, 0, 14, 23, 0xFDE5380C),
193 roundParam(0, 1, 2, 3, 1, 4, 0xA4BEEA44),
194 roundParam(3, 0, 1, 2, 4, 11, 0x4BDECFA9),
195 roundParam(2, 3, 0, 1, 7, 16, 0xF6BB4B60),
196 roundParam(1, 2, 3, 0, 10, 23, 0xBEBFBC70),
197 roundParam(0, 1, 2, 3, 13, 4, 0x289B7EC6),
198 roundParam(3, 0, 1, 2, 0, 11, 0xEAA127FA),
199 roundParam(2, 3, 0, 1, 3, 16, 0xD4EF3085),
200 roundParam(1, 2, 3, 0, 6, 23, 0x04881D05),
201 roundParam(0, 1, 2, 3, 9, 4, 0xD9D4D039),
202 roundParam(3, 0, 1, 2, 12, 11, 0xE6DB99E5),
203 roundParam(2, 3, 0, 1, 15, 16, 0x1FA27CF8),
204 roundParam(1, 2, 3, 0, 2, 23, 0xC4AC5665),
205 };
206 inline for (round2) |r| {
207 v[r.a] = v[r.a] +% (v[r.b] ^ v[r.c] ^ v[r.d]) +% r.t +% s[r.k];
208 v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
209 }
210
211 const round3 = comptime [_]RoundParam{
212 roundParam(0, 1, 2, 3, 0, 6, 0xF4292244),
213 roundParam(3, 0, 1, 2, 7, 10, 0x432AFF97),
214 roundParam(2, 3, 0, 1, 14, 15, 0xAB9423A7),
215 roundParam(1, 2, 3, 0, 5, 21, 0xFC93A039),
216 roundParam(0, 1, 2, 3, 12, 6, 0x655B59C3),
217 roundParam(3, 0, 1, 2, 3, 10, 0x8F0CCC92),
218 roundParam(2, 3, 0, 1, 10, 15, 0xFFEFF47D),
219 roundParam(1, 2, 3, 0, 1, 21, 0x85845DD1),
220 roundParam(0, 1, 2, 3, 8, 6, 0x6FA87E4F),
221 roundParam(3, 0, 1, 2, 15, 10, 0xFE2CE6E0),
222 roundParam(2, 3, 0, 1, 6, 15, 0xA3014314),
223 roundParam(1, 2, 3, 0, 13, 21, 0x4E0811A1),
224 roundParam(0, 1, 2, 3, 4, 6, 0xF7537E82),
225 roundParam(3, 0, 1, 2, 11, 10, 0xBD3AF235),
226 roundParam(2, 3, 0, 1, 2, 15, 0x2AD7D2BB),
227 roundParam(1, 2, 3, 0, 9, 21, 0xEB86D391),
228 };
229 inline for (round3) |r| {
230 v[r.a] = v[r.a] +% (v[r.c] ^ (v[r.b] | ~v[r.d])) +% r.t +% s[r.k];
231 v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
232 }
233
234 d.s[0] +%= v[0];
235 d.s[1] +%= v[1];
236 d.s[2] +%= v[2];
237 d.s[3] +%= v[3];
238 }
239};
240
241const htest = @import("test.zig");
242
243test "single" {
244 try htest.assertEqualHash(Md5, "d41d8cd98f00b204e9800998ecf8427e", "");
245 try htest.assertEqualHash(Md5, "0cc175b9c0f1b6a831c399e269772661", "a");
246 try htest.assertEqualHash(Md5, "900150983cd24fb0d6963f7d28e17f72", "abc");
247 try htest.assertEqualHash(Md5, "f96b697d7cb7938d525a2f31aaf161d0", "message digest");
248 try htest.assertEqualHash(Md5, "c3fcd3d76192e4007dfb496cca67e13b", "abcdefghijklmnopqrstuvwxyz");
249 try htest.assertEqualHash(Md5, "d174ab98d277d9f5a5611c2c9f419d9f", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
250 try htest.assertEqualHash(Md5, "57edf4a22be3c955ac49da2e2107b67a", "12345678901234567890123456789012345678901234567890123456789012345678901234567890");
251}
252
253test "streaming" {
254 var h = Md5.init(.{});
255 var out: [16]u8 = undefined;
256
257 h.final(out[0..]);
258 try htest.assertEqual("d41d8cd98f00b204e9800998ecf8427e", out[0..]);
259
260 h = Md5.init(.{});
261 h.update("abc");
262 h.final(out[0..]);
263 try htest.assertEqual("900150983cd24fb0d6963f7d28e17f72", out[0..]);
264
265 h = Md5.init(.{});
266 h.update("a");
267 h.update("b");
268 h.update("c");
269 h.final(out[0..]);
270
271 try htest.assertEqual("900150983cd24fb0d6963f7d28e17f72", out[0..]);
272}
273
274test "aligned final" {
275 var block = [_]u8{0} ** Md5.block_length;
276 var out: [Md5.digest_length]u8 = undefined;
277
278 var h = Md5.init(.{});
279 h.update(&block);
280 h.final(out[0..]);
281}