use crate::utils::encoders::{to_base32, to_base64, to_base64url, to_binary_string, to_hex_string};
use crate::BinaryToTextEncoding;
use core::convert::TryFrom;
const INITIAL_STATE: [u32; 4] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476];
#[derive(Copy, 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]) -> Self {
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;
}
}
*self
}
pub fn digest(mut self, encoding: BinaryToTextEncoding) -> String {
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 hash = [0u8; Self::DIGEST_LEN];
hash[0..4].copy_from_slice(&self.state[0].to_le_bytes());
hash[4..8].copy_from_slice(&self.state[1].to_le_bytes());
hash[8..12].copy_from_slice(&self.state[2].to_le_bytes());
hash[12..16].copy_from_slice(&self.state[3].to_le_bytes());
match encoding {
BinaryToTextEncoding::Base32 => to_base32(&hash),
BinaryToTextEncoding::Base64 => to_base64(&hash),
BinaryToTextEncoding::Base64Url => to_base64url(to_base64(&hash)),
BinaryToTextEncoding::Hex => to_hex_string(&hash),
BinaryToTextEncoding::Binary => to_binary_string(&hash),
}
}
}
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);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_md4() {
let sut = Md4::new()
.update(b"The quick brown fox jumps over the lazy dog")
.digest(BinaryToTextEncoding::Hex);
assert_eq!(sut, "1bee69a46ba811185c194762abaeae90");
}
}