use crate::read_u32;
const INITIAL_STATE: [u32; 4] = [0x6745_2301, 0xefcd_ab89, 0x98ba_dcfe, 0x1032_5476];
#[must_use]
#[inline]
pub fn md5(input: &[u8]) -> [u8; 16] {
let mut state = Md5::new();
state.update(input);
state.digest()
}
#[derive(Clone, Debug)]
pub struct Md5 {
state: [u32; 4],
buffer: [u8; 64],
buffered: usize,
total_len: u64,
}
impl Md5 {
#[must_use]
pub const fn new() -> Self {
Self {
state: INITIAL_STATE,
buffer: [0; 64],
buffered: 0,
total_len: 0,
}
}
#[inline]
pub fn update(&mut self, mut input: &[u8]) {
self.total_len = self.total_len.wrapping_add(input.len() as u64);
if self.buffered != 0 {
let copied = (64 - self.buffered).min(input.len());
self.buffer[self.buffered..self.buffered + copied].copy_from_slice(&input[..copied]);
self.buffered += copied;
input = &input[copied..];
if self.buffered < 64 {
return;
}
compress(&mut self.state, &self.buffer);
self.buffered = 0;
}
while let Some((block, rest)) = input.split_first_chunk::<64>() {
compress(&mut self.state, block);
input = rest;
}
self.buffer[..input.len()].copy_from_slice(input);
self.buffered = input.len();
}
#[must_use]
#[inline]
pub fn digest(&self) -> [u8; 16] {
let mut state = self.state;
let mut block = [0; 64];
block[..self.buffered].copy_from_slice(&self.buffer[..self.buffered]);
block[self.buffered] = 0x80;
if self.buffered >= 56 {
compress(&mut state, &block);
block = [0; 64];
}
block[56..].copy_from_slice(&self.total_len.wrapping_mul(8).to_le_bytes());
compress(&mut state, &block);
let mut digest = [0; 16];
for (word, bytes) in state.iter().zip(digest.chunks_exact_mut(4)) {
bytes.copy_from_slice(&word.to_le_bytes());
}
digest
}
}
impl Default for Md5 {
fn default() -> Self {
Self::new()
}
}
fn compress(state: &mut [u32; 4], block: &[u8; 64]) {
let mut words = [0; 16];
for (index, word) in words.iter_mut().enumerate() {
*word = read_u32(block, index * 4);
}
let [mut a, mut b, mut c, mut d] = *state;
macro_rules! step {
($a:ident, $b:ident, $mix:expr, $word:literal, $shift:literal, $constant:literal) => {
$a = $b.wrapping_add(
$a.wrapping_add($mix)
.wrapping_add(words[$word])
.wrapping_add($constant)
.rotate_left($shift),
);
};
}
step!(a, b, (b & c) | (!b & d), 0, 7, 0xd76a_a478);
step!(d, a, (a & b) | (!a & c), 1, 12, 0xe8c7_b756);
step!(c, d, (d & a) | (!d & b), 2, 17, 0x2420_70db);
step!(b, c, (c & d) | (!c & a), 3, 22, 0xc1bd_ceee);
step!(a, b, (b & c) | (!b & d), 4, 7, 0xf57c_0faf);
step!(d, a, (a & b) | (!a & c), 5, 12, 0x4787_c62a);
step!(c, d, (d & a) | (!d & b), 6, 17, 0xa830_4613);
step!(b, c, (c & d) | (!c & a), 7, 22, 0xfd46_9501);
step!(a, b, (b & c) | (!b & d), 8, 7, 0x6980_98d8);
step!(d, a, (a & b) | (!a & c), 9, 12, 0x8b44_f7af);
step!(c, d, (d & a) | (!d & b), 10, 17, 0xffff_5bb1);
step!(b, c, (c & d) | (!c & a), 11, 22, 0x895c_d7be);
step!(a, b, (b & c) | (!b & d), 12, 7, 0x6b90_1122);
step!(d, a, (a & b) | (!a & c), 13, 12, 0xfd98_7193);
step!(c, d, (d & a) | (!d & b), 14, 17, 0xa679_438e);
step!(b, c, (c & d) | (!c & a), 15, 22, 0x49b4_0821);
step!(a, b, (b & d) | (c & !d), 1, 5, 0xf61e_2562);
step!(d, a, (a & c) | (b & !c), 6, 9, 0xc040_b340);
step!(c, d, (d & b) | (a & !b), 11, 14, 0x265e_5a51);
step!(b, c, (c & a) | (d & !a), 0, 20, 0xe9b6_c7aa);
step!(a, b, (b & d) | (c & !d), 5, 5, 0xd62f_105d);
step!(d, a, (a & c) | (b & !c), 10, 9, 0x0244_1453);
step!(c, d, (d & b) | (a & !b), 15, 14, 0xd8a1_e681);
step!(b, c, (c & a) | (d & !a), 4, 20, 0xe7d3_fbc8);
step!(a, b, (b & d) | (c & !d), 9, 5, 0x21e1_cde6);
step!(d, a, (a & c) | (b & !c), 14, 9, 0xc337_07d6);
step!(c, d, (d & b) | (a & !b), 3, 14, 0xf4d5_0d87);
step!(b, c, (c & a) | (d & !a), 8, 20, 0x455a_14ed);
step!(a, b, (b & d) | (c & !d), 13, 5, 0xa9e3_e905);
step!(d, a, (a & c) | (b & !c), 2, 9, 0xfcef_a3f8);
step!(c, d, (d & b) | (a & !b), 7, 14, 0x676f_02d9);
step!(b, c, (c & a) | (d & !a), 12, 20, 0x8d2a_4c8a);
step!(a, b, b ^ c ^ d, 5, 4, 0xfffa_3942);
step!(d, a, a ^ b ^ c, 8, 11, 0x8771_f681);
step!(c, d, d ^ a ^ b, 11, 16, 0x6d9d_6122);
step!(b, c, c ^ d ^ a, 14, 23, 0xfde5_380c);
step!(a, b, b ^ c ^ d, 1, 4, 0xa4be_ea44);
step!(d, a, a ^ b ^ c, 4, 11, 0x4bde_cfa9);
step!(c, d, d ^ a ^ b, 7, 16, 0xf6bb_4b60);
step!(b, c, c ^ d ^ a, 10, 23, 0xbebf_bc70);
step!(a, b, b ^ c ^ d, 13, 4, 0x289b_7ec6);
step!(d, a, a ^ b ^ c, 0, 11, 0xeaa1_27fa);
step!(c, d, d ^ a ^ b, 3, 16, 0xd4ef_3085);
step!(b, c, c ^ d ^ a, 6, 23, 0x0488_1d05);
step!(a, b, b ^ c ^ d, 9, 4, 0xd9d4_d039);
step!(d, a, a ^ b ^ c, 12, 11, 0xe6db_99e5);
step!(c, d, d ^ a ^ b, 15, 16, 0x1fa2_7cf8);
step!(b, c, c ^ d ^ a, 2, 23, 0xc4ac_5665);
step!(a, b, c ^ (b | !d), 0, 6, 0xf429_2244);
step!(d, a, b ^ (a | !c), 7, 10, 0x432a_ff97);
step!(c, d, a ^ (d | !b), 14, 15, 0xab94_23a7);
step!(b, c, d ^ (c | !a), 5, 21, 0xfc93_a039);
step!(a, b, c ^ (b | !d), 12, 6, 0x655b_59c3);
step!(d, a, b ^ (a | !c), 3, 10, 0x8f0c_cc92);
step!(c, d, a ^ (d | !b), 10, 15, 0xffef_f47d);
step!(b, c, d ^ (c | !a), 1, 21, 0x8584_5dd1);
step!(a, b, c ^ (b | !d), 8, 6, 0x6fa8_7e4f);
step!(d, a, b ^ (a | !c), 15, 10, 0xfe2c_e6e0);
step!(c, d, a ^ (d | !b), 6, 15, 0xa301_4314);
step!(b, c, d ^ (c | !a), 13, 21, 0x4e08_11a1);
step!(a, b, c ^ (b | !d), 4, 6, 0xf753_7e82);
step!(d, a, b ^ (a | !c), 11, 10, 0xbd3a_f235);
step!(c, d, a ^ (d | !b), 2, 15, 0x2ad7_d2bb);
step!(b, c, d ^ (c | !a), 9, 21, 0xeb86_d391);
for (word, value) in state.iter_mut().zip([a, b, c, d]) {
*word = word.wrapping_add(value);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rfc_vectors() {
let vectors: &[(&[u8], u128)] = &[
(b"", 0xd41d8cd98f00b204e9800998ecf8427e),
(b"a", 0x0cc175b9c0f1b6a831c399e269772661),
(b"abc", 0x900150983cd24fb0d6963f7d28e17f72),
(b"message digest", 0xf96b697d7cb7938d525a2f31aaf161d0),
(
b"abcdefghijklmnopqrstuvwxyz",
0xc3fcd3d76192e4007dfb496cca67e13b,
),
(
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
0xd174ab98d277d9f5a5611c2c9f419d9f,
),
(
b"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
0x57edf4a22be3c955ac49da2e2107b67a,
),
];
for &(input, expected) in vectors {
assert_eq!(md5(input), expected.to_be_bytes());
let mut state = Md5::new();
for byte in input {
state.update(core::slice::from_ref(byte));
}
assert_eq!(state.digest(), expected.to_be_bytes());
}
}
#[test]
fn digest_preserves_state_for_further_updates() {
let mut state = Md5::default();
state.update(b"a");
assert_eq!(
state.digest(),
0x0cc175b9c0f1b6a831c399e269772661u128.to_be_bytes()
);
assert_eq!(
state.digest(),
0x0cc175b9c0f1b6a831c399e269772661u128.to_be_bytes()
);
state.update(b"bc");
assert_eq!(
state.digest(),
0x900150983cd24fb0d6963f7d28e17f72u128.to_be_bytes()
);
}
}