md5_core/
lib.rs

1use std::num::Wrapping;
2
3pub struct Md5 {
4    buffer: Vec<u8>,
5    length: u64,
6    a0: u32,
7    b0: u32,
8    c0: u32,
9    d0: u32,
10}
11
12impl Md5 {
13    const PRECOMPUTED_TABLE: [u32; 64] = [
14        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613,
15        0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193,
16        0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d,
17        0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
18        0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122,
19        0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
20        0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244,
21        0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
22        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb,
23        0xeb86d391,
24    ];
25
26    const SHIFT_TABLE: [u32; 64] = [
27        7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5,
28        9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10,
29        15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
30    ];
31
32    pub fn new() -> Self {
33        Self {
34            buffer: Vec::new(),
35            length: 0,
36            a0: 0x67452301,
37            b0: 0xEFCDAB89,
38            c0: 0x98BADCFE,
39            d0: 0x10325476,
40        }
41    }
42
43    /// Returns a new Md5 object with the updated state of the md5 calculation
44    /// It means that this function is pure (no mutations)
45    ///
46    /// # Example
47    ///
48    /// ```
49    /// let mut md5 = md5_core::Md5::new();
50    /// md5 = md5.consume(b"hello");
51    /// ```
52    pub fn consume(&self, data: &[u8]) -> Self {
53        let mut buffer = [&self.buffer, data].concat();
54        let mut a0 = self.a0;
55        let mut b0 = self.b0;
56        let mut c0 = self.c0;
57        let mut d0 = self.d0;
58
59        while buffer.len() >= 64 {
60            let digested = Md5::calculate_chunks(&buffer[..64], a0, b0, c0, d0);
61            a0 = (((digested >> 96) & 0xffffffff) as u32).to_be();
62            b0 = (((digested >> 64) & 0xffffffff) as u32).to_be();
63            c0 = (((digested >> 32) & 0xffffffff) as u32).to_be();
64            d0 = (((digested >> 00) & 0xffffffff) as u32).to_be();
65            buffer = buffer[64..].to_vec();
66        }
67
68        Self {
69            buffer,
70            length: self.length + (data.len() as u64),
71            a0,
72            b0,
73            c0,
74            d0,
75        }
76    }
77
78    /// # Example
79    ///
80    /// ```
81    /// let mut md5 = md5_core::Md5::new();
82    /// md5 = md5.consume(b"hello");
83    /// md5 = md5.consume(b"world");
84    /// assert_eq!(md5.digest(), 0xfc5e038d38a57032085441e7fe7010b0);
85    /// ```
86    pub fn digest(&self) -> u128 {
87        let preprocessed = Self::preprocess(&self.buffer, self.length * 8);
88
89        return Md5::calculate_chunks(&preprocessed, self.a0, self.b0, self.c0, self.d0);
90    }
91
92    /// Returns the md5 hash of the input byte array
93    ///
94    /// # Limitations
95    ///
96    /// Works only with complete bytes (multiples of 8 bits)
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// assert_eq!(
102    ///     md5_core::Md5::calculate(b"helloworld"),
103    ///     0xfc5e038d38a57032085441e7fe7010b0
104    /// );
105    /// ```
106    pub fn calculate(input: &[u8]) -> u128 {
107        let preprocessed = Self::preprocess(input, (input.len() * 8).try_into().unwrap());
108
109        return Md5::calculate_chunks(
110            &preprocessed,
111            0x67452301u32,
112            0xEFCDAB89u32,
113            0x98BADCFEu32,
114            0x10325476u32,
115        );
116    }
117
118    fn calculate_chunks(buffer: &[u8], a0: u32, b0: u32, c0: u32, d0: u32) -> u128 {
119        let mut a0 = Wrapping(a0);
120        let mut b0 = Wrapping(b0);
121        let mut c0 = Wrapping(c0);
122        let mut d0 = Wrapping(d0);
123
124        for n in (0..buffer.len()).step_by(64) {
125            let (a, b, c, d) =
126                Md5::calculate_chunk(&buffer[n..n + 64].try_into().unwrap(), a0, b0, c0, d0);
127
128            a0 += a;
129            b0 += b;
130            c0 += c;
131            d0 += d;
132        }
133
134        return Md5::construct_digest(a0.0, b0.0, c0.0, d0.0);
135    }
136
137    fn calculate_chunk(
138        chunk: &[u8; 64],
139        mut a: Wrapping<u32>,
140        mut b: Wrapping<u32>,
141        mut c: Wrapping<u32>,
142        mut d: Wrapping<u32>,
143    ) -> (Wrapping<u32>, Wrapping<u32>, Wrapping<u32>, Wrapping<u32>) {
144        let m = [
145            Wrapping(Self::as_u32_le(&chunk[..4].try_into().unwrap())),
146            Wrapping(Self::as_u32_le(&chunk[4..8].try_into().unwrap())),
147            Wrapping(Self::as_u32_le(&chunk[8..12].try_into().unwrap())),
148            Wrapping(Self::as_u32_le(&chunk[12..16].try_into().unwrap())),
149            Wrapping(Self::as_u32_le(&chunk[16..20].try_into().unwrap())),
150            Wrapping(Self::as_u32_le(&chunk[20..24].try_into().unwrap())),
151            Wrapping(Self::as_u32_le(&chunk[24..28].try_into().unwrap())),
152            Wrapping(Self::as_u32_le(&chunk[28..32].try_into().unwrap())),
153            Wrapping(Self::as_u32_le(&chunk[32..36].try_into().unwrap())),
154            Wrapping(Self::as_u32_le(&chunk[36..40].try_into().unwrap())),
155            Wrapping(Self::as_u32_le(&chunk[40..44].try_into().unwrap())),
156            Wrapping(Self::as_u32_le(&chunk[44..48].try_into().unwrap())),
157            Wrapping(Self::as_u32_le(&chunk[48..52].try_into().unwrap())),
158            Wrapping(Self::as_u32_le(&chunk[52..56].try_into().unwrap())),
159            Wrapping(Self::as_u32_le(&chunk[56..60].try_into().unwrap())),
160            Wrapping(Self::as_u32_le(&chunk[60..64].try_into().unwrap())),
161        ];
162
163        for i in 0..64 {
164            let mut f;
165            let g: u32;
166
167            if i < 16 {
168                f = (b & c) | (!b & d);
169                g = i;
170            } else if i < 32 {
171                f = (d & b) | (!d & c);
172                g = (5 * i + 1) % 16;
173            } else if i < 48 {
174                f = b ^ c ^ d;
175                g = (3 * i + 5) % 16;
176            } else {
177                f = c ^ (b | !d);
178                g = (7 * i) % 16;
179            }
180
181            f += a + m[g as usize] + Wrapping(Self::PRECOMPUTED_TABLE[i as usize]);
182            a = d;
183            d = c;
184            c = b;
185            b += Wrapping(u32::rotate_left(f.0, Self::SHIFT_TABLE[i as usize]));
186        }
187
188        return (a, b, c, d);
189    }
190
191    fn construct_digest(a0: u32, b0: u32, c0: u32, d0: u32) -> u128 {
192        ((a0.to_be() as u128) << 96)
193            + ((b0.to_be() as u128) << 64)
194            + ((c0.to_be() as u128) << 32)
195            + d0.to_be() as u128
196    }
197
198    fn preprocess(input: &[u8], original_length_in_bits: u64) -> Vec<u8> {
199        let mut preprocessed = input.to_owned();
200        let original_length = original_length_in_bits;
201
202        let mut n_bytes_to_push = 56 - (preprocessed.len() % 64);
203        if n_bytes_to_push <= 0 {
204            n_bytes_to_push = 64 + n_bytes_to_push;
205        }
206
207        // append bit '1'. The current implementation only works with complete bytes,
208        // so b'10000000 == 0x80
209        preprocessed.push(0x80);
210
211        // push enough zeros to have 448 (mod 512) bits
212        // n_bytes_to_push - 1 because already pushed 0x80 above
213        let mut bytes_to_push = vec![0 as u8; n_bytes_to_push - 1];
214        preprocessed.append(&mut bytes_to_push);
215
216        preprocessed.append(&mut Self::u64_to_vector_u8_be(original_length as u64));
217
218        return preprocessed;
219    }
220
221    fn u64_to_vector_u8_be(value: u64) -> Vec<u8> {
222        let array: [u8; 8] = [
223            ((value >> 00) & 0xff) as u8,
224            ((value >> 08) & 0xff) as u8,
225            ((value >> 16) & 0xff) as u8,
226            ((value >> 24) & 0xff) as u8,
227            ((value >> 32) & 0xff) as u8,
228            ((value >> 40) & 0xff) as u8,
229            ((value >> 48) & 0xff) as u8,
230            ((value >> 56) & 0xff) as u8,
231        ];
232
233        return array.to_vec();
234    }
235
236    fn as_u32_le(array: &[u8; 4]) -> u32 {
237        ((array[0] as u32) << 0)
238            + ((array[1] as u32) << 8)
239            + ((array[2] as u32) << 16)
240            + ((array[3] as u32) << 24)
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use crate::Md5;
247
248    #[test]
249    fn calculate_from_empty_returns_0xd41d8cd98f00b204e9800998ecf8427e() {
250        assert_eq!(Md5::calculate(b""), 0xd41d8cd98f00b204e9800998ecf8427e);
251    }
252
253    #[test]
254    fn calculate_from_helloworld_returns_0xfc5e038d38a57032085441e7fe7010b0() {
255        assert_eq!(
256            Md5::calculate(b"helloworld"),
257            0xfc5e038d38a57032085441e7fe7010b0
258        );
259    }
260
261    #[test]
262    fn calculate_from_448_bits_() {
263        assert_eq!(
264            Md5::calculate(b"Lorem ipsum dolor sit amet, consectetur adipiscing odio."),
265            0x2251013dde7bffaa1780cf66fbbaf4bb
266        );
267    }
268
269    #[test]
270    fn calculate_from_two_chunks() {
271        assert_eq!(
272            Md5::calculate(b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas iaculis efficitur magna ac sagittis. Nullam consectetur nisi non nibh posuere suscipit. Nam velit est, fringilla tincidunt eleifend nec, cursus sit amet metus. Suspendisse id lacus at risus sollicitudin volutpat id in urna. Pellentesque commodo iaculis lectus vitae pulvinar. Morbi ullamcorper ex nisl. Vivamus vel fringilla metus, sit amet malesuada justo. Fusce in lobortis velit. Mauris sed purus mauris. Aenean lobortis bibendum ex quis congue. Etiam sapien nulla, viverra ut lorem blandit."),
273            0xba5e84b5ac5785cca9f18469cc8e0193
274        );
275    }
276
277    #[test]
278    fn consume_empty_and_digest() {
279        let mut md5 = Md5::new();
280        md5 = md5.consume(b"");
281        assert_eq!(md5.digest(), 0xd41d8cd98f00b204e9800998ecf8427e);
282    }
283
284    #[test]
285    fn consume_twice_small_and_digest() {
286        let mut md5 = Md5::new();
287        md5 = md5.consume(b"hello");
288        md5 = md5.consume(b"world");
289        assert_eq!(md5.digest(), 0xfc5e038d38a57032085441e7fe7010b0);
290    }
291
292    #[test]
293    fn consume_twice_two_chunks_and_digest() {
294        let mut md5 = Md5::new();
295        md5 = md5.consume(b"Lorem ipsum dolor sit amet, consectetur adipiscing elit aliquam.");
296        md5 = md5.consume(b"Lorem ipsum dolor sit amet, consectetur adipiscing elit aliquam.");
297        assert_eq!(md5.digest(), 0xce13701da5de58af48900b63f2da47ca);
298    }
299}