1use super::Digest;
9
10const IV: [u32; 4] = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476];
12
13const S: [u32; 64] = [
15 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, 9,
16 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15,
17 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
18];
19
20const K: [u32; 64] = [
22 0xd76a_a478,
23 0xe8c7_b756,
24 0x2420_70db,
25 0xc1bd_ceee,
26 0xf57c_0faf,
27 0x4787_c62a,
28 0xa830_4613,
29 0xfd46_9501,
30 0x6980_98d8,
31 0x8b44_f7af,
32 0xffff_5bb1,
33 0x895c_d7be,
34 0x6b90_1122,
35 0xfd98_7193,
36 0xa679_438e,
37 0x49b4_0821,
38 0xf61e_2562,
39 0xc040_b340,
40 0x265e_5a51,
41 0xe9b6_c7aa,
42 0xd62f_105d,
43 0x0244_1453,
44 0xd8a1_e681,
45 0xe7d3_fbc8,
46 0x21e1_cde6,
47 0xc337_07d6,
48 0xf4d5_0d87,
49 0x455a_14ed,
50 0xa9e3_e905,
51 0xfcef_a3f8,
52 0x676f_02d9,
53 0x8d2a_4c8a,
54 0xfffa_3942,
55 0x8771_f681,
56 0x6d9d_6122,
57 0xfde5_380c,
58 0xa4be_ea44,
59 0x4bde_cfa9,
60 0xf6bb_4b60,
61 0xbebf_bc70,
62 0x289b_7ec6,
63 0xeaa1_27fa,
64 0xd4ef_3085,
65 0x0488_1d05,
66 0xd9d4_d039,
67 0xe6db_99e5,
68 0x1fa2_7cf8,
69 0xc4ac_5665,
70 0xf429_2244,
71 0x432a_ff97,
72 0xab94_23a7,
73 0xfc93_a039,
74 0x655b_59c3,
75 0x8f0c_cc92,
76 0xffef_f47d,
77 0x8584_5dd1,
78 0x6fa8_7e4f,
79 0xfe2c_e6e0,
80 0xa301_4314,
81 0x4e08_11a1,
82 0xf753_7e82,
83 0xbd3a_f235,
84 0x2ad7_d2bb,
85 0xeb86_d391,
86];
87
88#[inline]
89fn compress(state: &mut [u32; 4], block: &[u8; 64]) {
90 let mut schedule = [0u32; 16];
91 for (i, chunk) in block.chunks_exact(4).enumerate() {
92 schedule[i] = u32::from_le_bytes(chunk.try_into().expect("4-byte chunk"));
93 }
94
95 let mut a_reg = state[0];
96 let mut b_reg = state[1];
97 let mut c_reg = state[2];
98 let mut d_reg = state[3];
99
100 for round_idx in 0..64 {
101 let (mix, word_idx) = match round_idx {
102 0..=15 => ((b_reg & c_reg) | ((!b_reg) & d_reg), round_idx),
103 16..=31 => (
104 (d_reg & b_reg) | ((!d_reg) & c_reg),
105 (5 * round_idx + 1) % 16,
106 ),
107 32..=47 => (b_reg ^ c_reg ^ d_reg, (3 * round_idx + 5) % 16),
108 _ => (c_reg ^ (b_reg | (!d_reg)), (7 * round_idx) % 16),
109 };
110
111 let tmp = d_reg;
112 d_reg = c_reg;
113 c_reg = b_reg;
114 b_reg = b_reg.wrapping_add(
115 a_reg
116 .wrapping_add(mix)
117 .wrapping_add(K[round_idx])
118 .wrapping_add(schedule[word_idx])
119 .rotate_left(S[round_idx]),
120 );
121 a_reg = tmp;
122 }
123
124 state[0] = state[0].wrapping_add(a_reg);
125 state[1] = state[1].wrapping_add(b_reg);
126 state[2] = state[2].wrapping_add(c_reg);
127 state[3] = state[3].wrapping_add(d_reg);
128}
129
130#[derive(Clone)]
131pub struct Md5 {
132 state: [u32; 4],
133 block: [u8; 64],
134 pos: usize,
135 bit_len: u64,
136}
137
138impl Default for Md5 {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl Md5 {
145 pub const BLOCK_LEN: usize = 64;
146 pub const OUTPUT_LEN: usize = 16;
147
148 #[must_use]
149 pub fn new() -> Self {
150 Self {
151 state: IV,
152 block: [0u8; 64],
153 pos: 0,
154 bit_len: 0,
155 }
156 }
157
158 pub fn update(&mut self, mut data: &[u8]) {
159 while !data.is_empty() {
160 let take = (64 - self.pos).min(data.len());
161 self.block[self.pos..self.pos + take].copy_from_slice(&data[..take]);
162 self.pos += take;
163 data = &data[take..];
164
165 if self.pos == 64 {
166 compress(&mut self.state, &self.block);
167 self.block = [0u8; 64];
168 self.pos = 0;
169 self.bit_len = self.bit_len.wrapping_add(512);
170 }
171 }
172 }
173
174 #[must_use]
175 pub fn finalize(mut self) -> [u8; 16] {
176 self.bit_len = self.bit_len.wrapping_add((self.pos as u64) * 8);
177
178 self.block[self.pos] = 0x80;
179 self.pos += 1;
180
181 if self.pos > 56 {
182 self.block[self.pos..].fill(0);
183 compress(&mut self.state, &self.block);
184 self.block = [0u8; 64];
185 self.pos = 0;
186 }
187
188 self.block[self.pos..56].fill(0);
189 self.block[56..].copy_from_slice(&self.bit_len.to_le_bytes());
190 compress(&mut self.state, &self.block);
191
192 let mut out = [0u8; 16];
193 for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
194 chunk.copy_from_slice(&word.to_le_bytes());
195 }
196 out
197 }
198
199 #[must_use]
200 pub fn digest(data: &[u8]) -> [u8; 16] {
201 let mut h = Self::new();
202 h.update(data);
203 h.finalize()
204 }
205
206 fn finalize_into_reset(&mut self, out: &mut [u8; 16]) {
207 self.bit_len = self.bit_len.wrapping_add((self.pos as u64) * 8);
208
209 self.block[self.pos] = 0x80;
210 self.pos += 1;
211
212 if self.pos > 56 {
213 self.block[self.pos..].fill(0);
214 compress(&mut self.state, &self.block);
215 self.block = [0u8; 64];
216 self.pos = 0;
217 }
218
219 self.block[self.pos..56].fill(0);
220 self.block[56..].copy_from_slice(&self.bit_len.to_le_bytes());
221 compress(&mut self.state, &self.block);
222
223 for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
224 chunk.copy_from_slice(&word.to_le_bytes());
225 }
226
227 self.zeroize();
228 }
229}
230
231impl Digest for Md5 {
232 const BLOCK_LEN: usize = 64;
233 const OUTPUT_LEN: usize = 16;
234
235 fn new() -> Self {
236 Self::new()
237 }
238
239 fn update(&mut self, data: &[u8]) {
240 self.update(data);
241 }
242
243 fn finalize_into(self, out: &mut [u8]) {
244 assert_eq!(out.len(), 16, "wrong digest length");
245 out.copy_from_slice(&self.finalize());
246 }
247
248 fn finalize_reset(&mut self, out: &mut [u8]) {
249 let out: &mut [u8; 16] = out.try_into().expect("wrong digest length");
250 self.finalize_into_reset(out);
251 }
252
253 fn zeroize(&mut self) {
254 crate::ct::zeroize_slice(self.state.as_mut_slice());
255 crate::ct::zeroize_slice(self.block.as_mut_slice());
256 self.pos = 0;
257 self.bit_len = 0;
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn hex(bytes: &[u8]) -> String {
266 let mut out = String::with_capacity(bytes.len() * 2);
267 for b in bytes {
268 use core::fmt::Write;
269 let _ = write!(&mut out, "{b:02x}");
270 }
271 out
272 }
273
274 #[test]
275 fn md5_empty() {
276 assert_eq!(hex(&Md5::digest(b"")), "d41d8cd98f00b204e9800998ecf8427e");
277 }
278
279 #[test]
280 fn md5_streaming_abc() {
281 let mut h = Md5::new();
282 h.update(b"a");
283 h.update(b"b");
284 h.update(b"c");
285 assert_eq!(hex(&h.finalize()), "900150983cd24fb0d6963f7d28e17f72");
286 }
287
288 #[test]
289 fn md5_known_vector() {
290 assert_eq!(
291 hex(&Md5::digest(b"message digest")),
292 "f96b697d7cb7938d525a2f31aaf161d0"
293 );
294 }
295
296 #[test]
297 fn md5_matches_openssl() {
298 let msg = b"The quick brown fox jumps over the lazy dog";
299 let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-md5", "-binary"], msg) else {
300 return;
301 };
302 assert_eq!(Md5::digest(msg).as_slice(), expected.as_slice());
303 }
304}