use core::convert::TryFrom;
const INITIAL_STATE: [u32; 4] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476];
pub fn md4<T: AsRef<[u8]>>(data: T) -> [u8; Md4::DIGEST_LEN] {
Md4::oneshot(data)
}
#[derive(Clone)]
pub struct Md4 {
buffer: [u8; Self::BLOCK_LEN],
state: [u32; 4],
len: u64, offset: usize,
}
impl Md4 {
pub const BLOCK_LEN: usize = 64;
pub const DIGEST_LEN: usize = 16;
const BLOCK_LEN_BITS: u64 = Self::BLOCK_LEN as u64 * 8;
const MLEN_SIZE: usize = core::mem::size_of::<u64>();
const MLEN_SIZE_BITS: u64 = Self::MLEN_SIZE as u64 * 8;
const MAX_PAD_LEN: usize = Self::BLOCK_LEN + Self::MLEN_SIZE as usize;
pub fn new() -> Self {
Self {
buffer: [0u8; 64],
state: INITIAL_STATE,
len: 0,
offset: 0,
}
}
pub fn update(&mut self, data: &[u8]) {
let mut i = 0usize;
while i < data.len() {
if self.offset < Self::BLOCK_LEN {
self.buffer[self.offset] = data[i];
self.offset += 1;
i += 1;
}
if self.offset == Self::BLOCK_LEN {
transform(&mut self.state, &self.buffer);
self.offset = 0;
self.len += Self::BLOCK_LEN as u64;
}
}
}
pub fn finalize(mut self) -> [u8; Self::DIGEST_LEN] {
let mlen = self.len + self.offset as u64; let mlen_bits = mlen * 8;
let plen_bits = Self::BLOCK_LEN_BITS - (mlen_bits + Self::MLEN_SIZE_BITS + 1) % Self::BLOCK_LEN_BITS + 1;
let plen = plen_bits / 8;
debug_assert_eq!(plen_bits % 8, 0);
debug_assert!(plen > 1);
debug_assert_eq!((mlen + plen + Self::MLEN_SIZE as u64) % Self::BLOCK_LEN as u64, 0);
let plen = usize::try_from(plen).unwrap();
let mut padding: [u8; Self::MAX_PAD_LEN] = [0u8; Self::MAX_PAD_LEN];
padding[0] = 0x80;
let mlen_octets: [u8; Self::MLEN_SIZE] = mlen_bits.to_le_bytes();
padding[plen..plen + Self::MLEN_SIZE].copy_from_slice(&mlen_octets);
let data = &padding[..plen + Self::MLEN_SIZE];
self.update(data);
debug_assert_eq!(self.offset, 0);
let mut output = [0u8; Self::DIGEST_LEN];
output[ 0.. 4].copy_from_slice(&self.state[0].to_le_bytes());
output[ 4.. 8].copy_from_slice(&self.state[1].to_le_bytes());
output[ 8..12].copy_from_slice(&self.state[2].to_le_bytes());
output[12..16].copy_from_slice(&self.state[3].to_le_bytes());
output
}
pub fn oneshot<T: AsRef<[u8]>>(data: T) -> [u8; Self::DIGEST_LEN] {
let mut m = Self::new();
m.update(data.as_ref());
m.finalize()
}
}
macro_rules! F {
($x:expr, $y:expr, $z:expr) => (
( ($x) & ($y) ) | ( !($x) & ($z) )
)
}
macro_rules! G {
($x:expr, $y:expr, $z:expr) => (
(($x) & ($y)) | (($x) & ($z)) | (($y) & ($z))
)
}
macro_rules! H {
($x:expr, $y:expr, $z:expr) => (
($x) ^ ($y) ^ ($z)
)
}
macro_rules! FF {
($a:expr, $b:expr, $c:expr, $d:expr, $k:expr, $s:expr) => (
$a.wrapping_add(F!($b, $c, $d)).wrapping_add($k).rotate_left($s)
)
}
macro_rules! GG {
($a:expr, $b:expr, $c:expr, $d:expr, $k:expr, $s:expr) => (
$a.wrapping_add(G!($b, $c, $d)).wrapping_add($k).wrapping_add(0x5A827999).rotate_left($s)
)
}
macro_rules! HH {
($a:expr, $b:expr, $c:expr, $d:expr, $k:expr, $s:expr) => (
$a.wrapping_add(H!($b, $c, $d)).wrapping_add($k).wrapping_add(0x6ED9EBA1).rotate_left($s)
)
}
#[inline]
fn transform(state: &mut [u32; 4], block: &[u8]) {
debug_assert_eq!(state.len(), 4);
debug_assert_eq!(block.len(), Md4::BLOCK_LEN);
let mut a = state[0];
let mut b = state[1];
let mut c = state[2];
let mut d = state[3];
let mut data = [0u32; 16];
for i in 0usize..16 {
let idx = i * 4;
data[i] = u32::from_le_bytes([
block[idx + 0],
block[idx + 1],
block[idx + 2],
block[idx + 3],
]);
}
for &i in &[0usize, 4, 8, 12] {
a = FF!(a, b, c, d, data[i], 3);
d = FF!(d, a, b, c, data[i + 1], 7);
c = FF!(c, d, a, b, data[i + 2], 11);
b = FF!(b, c, d, a, data[i + 3], 19);
}
for i in 0..4 {
a = GG!(a, b, c, d, data[i], 3);
d = GG!(d, a, b, c, data[i + 4], 5);
c = GG!(c, d, a, b, data[i + 8], 9);
b = GG!(b, c, d, a, data[i + 12], 13);
}
for &i in &[0usize, 2, 1, 3] {
a = HH!(a, b, c, d, data[i], 3);
d = HH!(d, a, b, c, data[i + 8], 9);
c = HH!(c, d, a, b, data[i + 4], 11);
b = HH!(b, c, d, a, data[i + 12], 15);
}
state[0] = state[0].wrapping_add(a);
state[1] = state[1].wrapping_add(b);
state[2] = state[2].wrapping_add(c);
state[3] = state[3].wrapping_add(d);
}
#[test]
fn test_md4() {
assert_eq!(&md4(""),
&hex::decode("31d6cfe0d16ae931b73c59d7e0c089c0").unwrap()[..]);
assert_eq!(&md4("a"),
&hex::decode("bde52cb31de33e46245e05fbdbd6fb24").unwrap()[..]);
assert_eq!(&md4("abc"),
&hex::decode("a448017aaf21d8525fc10ae87aa6729d").unwrap()[..]);
assert_eq!(&md4("message digest"),
&hex::decode("d9130a8164549fe818874806e1c7014b").unwrap()[..]);
assert_eq!(&md4("abcdefghijklmnopqrstuvwxyz"),
&hex::decode("d79e1c308aa5bbcdeea8ed63df412da9").unwrap()[..]);
assert_eq!(&md4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"),
&hex::decode("043f8582f241db351ce627e153e7f0e4").unwrap()[..]);
assert_eq!(&md4("12345678901234567890123456789012345678901234567890123456789012345678901234567890"),
&hex::decode("e33b4ddc9c38f2199c3e7b164fcc0536").unwrap()[..]);
}