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 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 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 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 preprocessed.push(0x80);
210
211 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}