Skip to main content

aoc/util/
md5.rs

1//! MD5 hash algorithm.
2//!
3//! Computes a 128-bit [MD5 hash](https://en.wikipedia.org/wiki/MD5) for a slice of `u8`.
4//! The hash is returned as a tuple of four `u32` values.
5//!
6//! The slice is modified in place and must be a multiple of 64 bytes long with at least 9 bytes
7//! spare for the MD5 padding.
8//!
9//! To maximize speed, the loop for each of the four rounds used to create the hash is unrolled and
10//! all internal utility functions marked as
11//! [`#[inline]`](https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute).
12//!
13//! An optional SIMD variant that computes multiple hashes in parallel is also implemented.
14#[inline]
15#[must_use]
16pub fn hash(buffer: &mut [u8], size: usize) -> [u32; 4] {
17    let end = buffer.len() - 8;
18    let bits = size * 8;
19
20    buffer[size] = 0x80;
21    buffer[end..].copy_from_slice(&bits.to_le_bytes());
22
23    let mut m = [0; 16];
24    let [mut a0, mut b0, mut c0, mut d0] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476];
25
26    for block in buffer.chunks_exact(64) {
27        for (i, chunk) in block.chunks_exact(4).enumerate() {
28            m[i] = u32::from_le_bytes(chunk.try_into().unwrap());
29        }
30
31        let [mut a, mut b, mut c, mut d] = [a0, b0, c0, d0];
32
33        a = round1(a, b, c, d, m[0], 7, 0xd76aa478);
34        d = round1(d, a, b, c, m[1], 12, 0xe8c7b756);
35        c = round1(c, d, a, b, m[2], 17, 0x242070db);
36        b = round1(b, c, d, a, m[3], 22, 0xc1bdceee);
37        a = round1(a, b, c, d, m[4], 7, 0xf57c0faf);
38        d = round1(d, a, b, c, m[5], 12, 0x4787c62a);
39        c = round1(c, d, a, b, m[6], 17, 0xa8304613);
40        b = round1(b, c, d, a, m[7], 22, 0xfd469501);
41        a = round1(a, b, c, d, m[8], 7, 0x698098d8);
42        d = round1(d, a, b, c, m[9], 12, 0x8b44f7af);
43        c = round1(c, d, a, b, m[10], 17, 0xffff5bb1);
44        b = round1(b, c, d, a, m[11], 22, 0x895cd7be);
45        a = round1(a, b, c, d, m[12], 7, 0x6b901122);
46        d = round1(d, a, b, c, m[13], 12, 0xfd987193);
47        c = round1(c, d, a, b, m[14], 17, 0xa679438e);
48        b = round1(b, c, d, a, m[15], 22, 0x49b40821);
49
50        a = round2(a, b, c, d, m[1], 5, 0xf61e2562);
51        d = round2(d, a, b, c, m[6], 9, 0xc040b340);
52        c = round2(c, d, a, b, m[11], 14, 0x265e5a51);
53        b = round2(b, c, d, a, m[0], 20, 0xe9b6c7aa);
54        a = round2(a, b, c, d, m[5], 5, 0xd62f105d);
55        d = round2(d, a, b, c, m[10], 9, 0x02441453);
56        c = round2(c, d, a, b, m[15], 14, 0xd8a1e681);
57        b = round2(b, c, d, a, m[4], 20, 0xe7d3fbc8);
58        a = round2(a, b, c, d, m[9], 5, 0x21e1cde6);
59        d = round2(d, a, b, c, m[14], 9, 0xc33707d6);
60        c = round2(c, d, a, b, m[3], 14, 0xf4d50d87);
61        b = round2(b, c, d, a, m[8], 20, 0x455a14ed);
62        a = round2(a, b, c, d, m[13], 5, 0xa9e3e905);
63        d = round2(d, a, b, c, m[2], 9, 0xfcefa3f8);
64        c = round2(c, d, a, b, m[7], 14, 0x676f02d9);
65        b = round2(b, c, d, a, m[12], 20, 0x8d2a4c8a);
66
67        a = round3(a, b, c, d, m[5], 4, 0xfffa3942);
68        d = round3(d, a, b, c, m[8], 11, 0x8771f681);
69        c = round3(c, d, a, b, m[11], 16, 0x6d9d6122);
70        b = round3(b, c, d, a, m[14], 23, 0xfde5380c);
71        a = round3(a, b, c, d, m[1], 4, 0xa4beea44);
72        d = round3(d, a, b, c, m[4], 11, 0x4bdecfa9);
73        c = round3(c, d, a, b, m[7], 16, 0xf6bb4b60);
74        b = round3(b, c, d, a, m[10], 23, 0xbebfbc70);
75        a = round3(a, b, c, d, m[13], 4, 0x289b7ec6);
76        d = round3(d, a, b, c, m[0], 11, 0xeaa127fa);
77        c = round3(c, d, a, b, m[3], 16, 0xd4ef3085);
78        b = round3(b, c, d, a, m[6], 23, 0x04881d05);
79        a = round3(a, b, c, d, m[9], 4, 0xd9d4d039);
80        d = round3(d, a, b, c, m[12], 11, 0xe6db99e5);
81        c = round3(c, d, a, b, m[15], 16, 0x1fa27cf8);
82        b = round3(b, c, d, a, m[2], 23, 0xc4ac5665);
83
84        a = round4(a, b, c, d, m[0], 6, 0xf4292244);
85        d = round4(d, a, b, c, m[7], 10, 0x432aff97);
86        c = round4(c, d, a, b, m[14], 15, 0xab9423a7);
87        b = round4(b, c, d, a, m[5], 21, 0xfc93a039);
88        a = round4(a, b, c, d, m[12], 6, 0x655b59c3);
89        d = round4(d, a, b, c, m[3], 10, 0x8f0ccc92);
90        c = round4(c, d, a, b, m[10], 15, 0xffeff47d);
91        b = round4(b, c, d, a, m[1], 21, 0x85845dd1);
92        a = round4(a, b, c, d, m[8], 6, 0x6fa87e4f);
93        d = round4(d, a, b, c, m[15], 10, 0xfe2ce6e0);
94        c = round4(c, d, a, b, m[6], 15, 0xa3014314);
95        b = round4(b, c, d, a, m[13], 21, 0x4e0811a1);
96        a = round4(a, b, c, d, m[4], 6, 0xf7537e82);
97        d = round4(d, a, b, c, m[11], 10, 0xbd3af235);
98        c = round4(c, d, a, b, m[2], 15, 0x2ad7d2bb);
99        b = round4(b, c, d, a, m[9], 21, 0xeb86d391);
100
101        [a0, b0, c0, d0] =
102            [a0.wrapping_add(a), b0.wrapping_add(b), c0.wrapping_add(c), d0.wrapping_add(d)];
103    }
104
105    [a0.to_be(), b0.to_be(), c0.to_be(), d0.to_be()]
106}
107
108#[inline]
109fn round1(a: u32, b: u32, c: u32, d: u32, m: u32, s: u32, k: u32) -> u32 {
110    let f = (b & c) | (!b & d);
111    common(f, a, b, m, s, k)
112}
113
114#[inline]
115fn round2(a: u32, b: u32, c: u32, d: u32, m: u32, s: u32, k: u32) -> u32 {
116    let f = (b & d) | (c & !d);
117    common(f, a, b, m, s, k)
118}
119
120#[inline]
121fn round3(a: u32, b: u32, c: u32, d: u32, m: u32, s: u32, k: u32) -> u32 {
122    let f = b ^ c ^ d;
123    common(f, a, b, m, s, k)
124}
125
126#[inline]
127fn round4(a: u32, b: u32, c: u32, d: u32, m: u32, s: u32, k: u32) -> u32 {
128    let f = c ^ (b | !d);
129    common(f, a, b, m, s, k)
130}
131
132#[inline]
133fn common(f: u32, a: u32, b: u32, m: u32, s: u32, k: u32) -> u32 {
134    f.wrapping_add(a).wrapping_add(k).wrapping_add(m).rotate_left(s).wrapping_add(b)
135}
136
137#[cfg(feature = "simd")]
138pub mod simd {
139    use std::array::from_fn;
140    use std::simd::prelude::*;
141
142    #[inline]
143    #[must_use]
144    pub fn hash_fixed<const N: usize>(
145        buffers: &mut [[u8; 64]; N],
146        size: usize,
147    ) -> [Simd<u32, N>; 4] {
148        // Assume all buffers are the same size.
149        for buffer in buffers.iter_mut() {
150            buffer[size] = 0x80;
151        }
152
153        let [a0, b0, c0, d0] = [
154            Simd::splat(0x67452301),
155            Simd::splat(0xefcdab89),
156            Simd::splat(0x98badcfe),
157            Simd::splat(0x10325476),
158        ];
159        let [mut a, mut b, mut c, mut d] = [a0, b0, c0, d0];
160
161        let m0 = message(buffers, 0, size);
162        a = round1(a, b, c, d, m0, 7, 0xd76aa478);
163        let m1 = message(buffers, 4, size);
164        d = round1(d, a, b, c, m1, 12, 0xe8c7b756);
165        let m2 = message(buffers, 8, size);
166        c = round1(c, d, a, b, m2, 17, 0x242070db);
167        let m3 = message(buffers, 12, size);
168        b = round1(b, c, d, a, m3, 22, 0xc1bdceee);
169        let m4 = message(buffers, 16, size);
170        a = round1(a, b, c, d, m4, 7, 0xf57c0faf);
171        let m5 = message(buffers, 20, size);
172        d = round1(d, a, b, c, m5, 12, 0x4787c62a);
173        let m6 = message(buffers, 24, size);
174        c = round1(c, d, a, b, m6, 17, 0xa8304613);
175        let m7 = message(buffers, 28, size);
176        b = round1(b, c, d, a, m7, 22, 0xfd469501);
177        let m8 = message(buffers, 32, size);
178        a = round1(a, b, c, d, m8, 7, 0x698098d8);
179        let m9 = message(buffers, 36, size);
180        d = round1(d, a, b, c, m9, 12, 0x8b44f7af);
181        let m10 = message(buffers, 40, size);
182        c = round1(c, d, a, b, m10, 17, 0xffff5bb1);
183        let m11 = message(buffers, 44, size);
184        b = round1(b, c, d, a, m11, 22, 0x895cd7be);
185        let m12 = message(buffers, 48, size);
186        a = round1(a, b, c, d, m12, 7, 0x6b901122);
187        let m13 = message(buffers, 52, size);
188        d = round1(d, a, b, c, m13, 12, 0xfd987193);
189        let m14 = Simd::splat(size as u32 * 8);
190        c = round1(c, d, a, b, m14, 17, 0xa679438e);
191        let m15 = Simd::splat(0);
192        b = round1(b, c, d, a, m15, 22, 0x49b40821);
193
194        a = round2(a, b, c, d, m1, 5, 0xf61e2562);
195        d = round2(d, a, b, c, m6, 9, 0xc040b340);
196        c = round2(c, d, a, b, m11, 14, 0x265e5a51);
197        b = round2(b, c, d, a, m0, 20, 0xe9b6c7aa);
198        a = round2(a, b, c, d, m5, 5, 0xd62f105d);
199        d = round2(d, a, b, c, m10, 9, 0x02441453);
200        c = round2(c, d, a, b, m15, 14, 0xd8a1e681);
201        b = round2(b, c, d, a, m4, 20, 0xe7d3fbc8);
202        a = round2(a, b, c, d, m9, 5, 0x21e1cde6);
203        d = round2(d, a, b, c, m14, 9, 0xc33707d6);
204        c = round2(c, d, a, b, m3, 14, 0xf4d50d87);
205        b = round2(b, c, d, a, m8, 20, 0x455a14ed);
206        a = round2(a, b, c, d, m13, 5, 0xa9e3e905);
207        d = round2(d, a, b, c, m2, 9, 0xfcefa3f8);
208        c = round2(c, d, a, b, m7, 14, 0x676f02d9);
209        b = round2(b, c, d, a, m12, 20, 0x8d2a4c8a);
210
211        a = round3(a, b, c, d, m5, 4, 0xfffa3942);
212        d = round3(d, a, b, c, m8, 11, 0x8771f681);
213        c = round3(c, d, a, b, m11, 16, 0x6d9d6122);
214        b = round3(b, c, d, a, m14, 23, 0xfde5380c);
215        a = round3(a, b, c, d, m1, 4, 0xa4beea44);
216        d = round3(d, a, b, c, m4, 11, 0x4bdecfa9);
217        c = round3(c, d, a, b, m7, 16, 0xf6bb4b60);
218        b = round3(b, c, d, a, m10, 23, 0xbebfbc70);
219        a = round3(a, b, c, d, m13, 4, 0x289b7ec6);
220        d = round3(d, a, b, c, m0, 11, 0xeaa127fa);
221        c = round3(c, d, a, b, m3, 16, 0xd4ef3085);
222        b = round3(b, c, d, a, m6, 23, 0x04881d05);
223        a = round3(a, b, c, d, m9, 4, 0xd9d4d039);
224        d = round3(d, a, b, c, m12, 11, 0xe6db99e5);
225        c = round3(c, d, a, b, m15, 16, 0x1fa27cf8);
226        b = round3(b, c, d, a, m2, 23, 0xc4ac5665);
227
228        a = round4(a, b, c, d, m0, 6, 0xf4292244);
229        d = round4(d, a, b, c, m7, 10, 0x432aff97);
230        c = round4(c, d, a, b, m14, 15, 0xab9423a7);
231        b = round4(b, c, d, a, m5, 21, 0xfc93a039);
232        a = round4(a, b, c, d, m12, 6, 0x655b59c3);
233        d = round4(d, a, b, c, m3, 10, 0x8f0ccc92);
234        c = round4(c, d, a, b, m10, 15, 0xffeff47d);
235        b = round4(b, c, d, a, m1, 21, 0x85845dd1);
236        a = round4(a, b, c, d, m8, 6, 0x6fa87e4f);
237        d = round4(d, a, b, c, m15, 10, 0xfe2ce6e0);
238        c = round4(c, d, a, b, m6, 15, 0xa3014314);
239        b = round4(b, c, d, a, m13, 21, 0x4e0811a1);
240        a = round4(a, b, c, d, m4, 6, 0xf7537e82);
241        d = round4(d, a, b, c, m11, 10, 0xbd3af235);
242        c = round4(c, d, a, b, m2, 15, 0x2ad7d2bb);
243        b = round4(b, c, d, a, m9, 21, 0xeb86d391);
244
245        [(a0 + a).swap_bytes(), (b0 + b).swap_bytes(), (c0 + c).swap_bytes(), (d0 + d).swap_bytes()]
246    }
247
248    #[inline]
249    fn message<const N: usize>(buffers: &[[u8; 64]; N], i: usize, size: usize) -> Simd<u32, N> {
250        if i > size {
251            Simd::splat(0)
252        } else {
253            Simd::from_array(from_fn(|lane| {
254                let slice = &buffers[lane][i..i + 4];
255                u32::from_le_bytes(slice.try_into().unwrap())
256            }))
257        }
258    }
259
260    #[inline]
261    fn round1<const N: usize>(
262        a: Simd<u32, N>,
263        b: Simd<u32, N>,
264        c: Simd<u32, N>,
265        d: Simd<u32, N>,
266        m: Simd<u32, N>,
267        s: u32,
268        k: u32,
269    ) -> Simd<u32, N> {
270        let f = (b & c) | (!b & d);
271        common(f, a, b, m, s, k)
272    }
273
274    #[inline]
275    fn round2<const N: usize>(
276        a: Simd<u32, N>,
277        b: Simd<u32, N>,
278        c: Simd<u32, N>,
279        d: Simd<u32, N>,
280        m: Simd<u32, N>,
281        s: u32,
282        k: u32,
283    ) -> Simd<u32, N> {
284        let f = (b & d) | (c & !d);
285        common(f, a, b, m, s, k)
286    }
287
288    #[inline]
289    fn round3<const N: usize>(
290        a: Simd<u32, N>,
291        b: Simd<u32, N>,
292        c: Simd<u32, N>,
293        d: Simd<u32, N>,
294        m: Simd<u32, N>,
295        s: u32,
296        k: u32,
297    ) -> Simd<u32, N> {
298        let f = b ^ c ^ d;
299        common(f, a, b, m, s, k)
300    }
301
302    #[inline]
303    fn round4<const N: usize>(
304        a: Simd<u32, N>,
305        b: Simd<u32, N>,
306        c: Simd<u32, N>,
307        d: Simd<u32, N>,
308        m: Simd<u32, N>,
309        s: u32,
310        k: u32,
311    ) -> Simd<u32, N> {
312        let f = c ^ (b | !d);
313        common(f, a, b, m, s, k)
314    }
315
316    #[inline]
317    fn common<const N: usize>(
318        f: Simd<u32, N>,
319        a: Simd<u32, N>,
320        b: Simd<u32, N>,
321        m: Simd<u32, N>,
322        s: u32,
323        k: u32,
324    ) -> Simd<u32, N> {
325        let k = Simd::splat(k);
326        let first = f + a + k + m;
327        let second = (first << s) | (first >> (32 - s));
328        second + b
329    }
330}