| 1 | const std = @import("../std.zig"); |
| 2 | const mem = std.mem; |
| 3 | const math = std.math; |
| 4 | |
| 5 | const 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 | |
| 15 | fn 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. |
| 30 | pub 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 | |
| 241 | const htest = @import("test.zig"); |
| 242 | |
| 243 | test "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 | |
| 253 | test "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 | |
| 274 | test "aligned final" { |
| 275 | const block: [Md5.block_length]u8 = @splat(0); |
| 276 | var out: [Md5.digest_length]u8 = undefined; |
| 277 | |
| 278 | var h = Md5.init(.{}); |
| 279 | h.update(&block); |
| 280 | h.final(out[0..]); |
| 281 | } |