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}