use std::num::Wrapping;
use crate::Misc;
use crate::Hasher::HasherTrait;
#[derive(Debug, Clone)]
pub struct MD5
{
a: Wrapping<u32>,
b: Wrapping<u32>,
c: Wrapping<u32>,
d: Wrapping<u32>,
leftoverInput: Vec<u8>,
inputLen: u64,
}
impl MD5
{
const BLOCK_SIZE: usize = 64;
const ROUND_SHIFT: [u32; 64] =
[
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, 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, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
];
const ROUND_CONSTANTS: [u32; 64] =
[
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
];
pub fn New () -> Self
{
Self
{
a: Wrapping(0x67452301),
b: Wrapping(0xefcdab89),
c: Wrapping(0x98badcfe),
d: Wrapping(0x10325476),
leftoverInput: Vec::new (),
inputLen: 0,
}
}
fn AddBlock (&mut self, input: &[u8; Self::BLOCK_SIZE])
{
let mut M = [Wrapping(0u32); 16];
for i in (0..input.len ()).step_by (4)
{
M[i/4] = Wrapping(u32::from_le_bytes (input[i..i + 4].try_into ().unwrap ()));
}
let mut a = self.a;
let mut b = self.b;
let mut c = self.c;
let mut d = self.d;
for i in 0..64
{
let (mut f, g) = match i
{
x if (0..16).contains (&x) => ((b & c) | ((!b) & d), i),
x if (16..32).contains (&x) => ((d & b) | ((!d) & c), (i*5 + 1) % 16),
x if (32..48).contains (&x) => (b ^ c ^ d, (i*3 + 5) % 16),
x if (48..64).contains (&x) => (c ^ (b | (!d)), (i*7) % 16),
_ => panic! ("Something went wrong!"),
};
f += a + Wrapping(Self::ROUND_CONSTANTS[i]) + M[g];
a = d;
d = c;
c = b;
b += f.rotate_left (Self::ROUND_SHIFT[i]);
}
self.a += a;
self.b += b;
self.c += c;
self.d += d;
}
pub fn AddInput (&mut self, input: &[u8])
{
if self.leftoverInput.len () + input.len () < Self::BLOCK_SIZE
{
self.leftoverInput.extend (input);
self.inputLen += u64::try_from (input.len ()).unwrap ();
return;
}
let buffer = [&self.leftoverInput, &input[0..Self::BLOCK_SIZE - self.leftoverInput.len ()]].concat ();
assert! (buffer.len () == Self::BLOCK_SIZE);
self.AddBlock (&buffer[0..Self::BLOCK_SIZE].try_into ().unwrap ());
for block in input[Self::BLOCK_SIZE - self.leftoverInput.len ()..].chunks_exact (Self::BLOCK_SIZE)
{
self.AddBlock (block.try_into ().unwrap ());
}
let x = Misc::AlignLeft::<usize> (input.len (), Self::BLOCK_SIZE);
let mut y = x + (Self::BLOCK_SIZE - self.leftoverInput.len ());
if y > input.len ()
{
y -= Self::BLOCK_SIZE;
}
self.leftoverInput.clear ();
self.leftoverInput.extend (&input[y..]);
self.inputLen += u64::try_from (input.len ()).unwrap ();
}
pub fn GetOutput (mut self) -> [u8; Self::OUTPUT_SIZE]
{
let paddingLen = Misc::Align::<u64> (self.inputLen + 1 + 8, Self::BLOCK_SIZE) - (self.inputLen + 1 + 8);
let lastBlock = [&[0x80][..], &vec! [0x00; paddingLen.try_into ().unwrap ()], &(self.inputLen*8).to_le_bytes ()].concat ();
self.AddInput (&lastBlock);
[self.a.0.to_le_bytes (), self.b.0.to_le_bytes (), self.c.0.to_le_bytes (), self.d.0.to_le_bytes ()].concat ().try_into ().unwrap ()
}
pub fn Hash (input: &[u8]) -> [u8; Self::OUTPUT_SIZE]
{
let mut hasher = Self::New ();
hasher.AddInput (input);
hasher.GetOutput ()
}
}
impl HasherTrait for MD5
{
const OUTPUT_SIZE: usize = 16;
fn AddInput (&mut self, input: &[u8])
{
self.AddInput (input);
}
fn GetOutput (self) -> [u8; Self::OUTPUT_SIZE]
{
self.GetOutput ()
}
fn Hash (input: &[u8]) -> [u8; Self::OUTPUT_SIZE]
{
Self::Hash (input)
}
}
#[cfg(test)]
mod tests
{
use super::*;
use hex;
#[test]
fn MD5_1 ()
{
let mut md5 = MD5::New ();
md5.AddInput ("".as_bytes ());
let output = md5.GetOutput ();
println!("{:?}", hex::encode (output));
assert! (hex::encode (output) == "d41d8cd98f00b204e9800998ecf8427e");
}
#[test]
fn MD5_2 ()
{
let mut md5 = MD5::New ();
md5.AddInput ("a".as_bytes ());
let output = md5.GetOutput ();
assert! (hex::encode (output) == "0cc175b9c0f1b6a831c399e269772661");
}
#[test]
fn MD5_3 ()
{
let mut md5 = MD5::New ();
md5.AddInput ("abc".as_bytes ());
let output = md5.GetOutput ();
assert! (hex::encode (output) == "900150983cd24fb0d6963f7d28e17f72");
}
#[test]
fn MD5_4 ()
{
let mut md5 = MD5::New ();
md5.AddInput ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".as_bytes ());
let output = md5.GetOutput ();
assert! (hex::encode (output) == "d174ab98d277d9f5a5611c2c9f419d9f");
}
#[test]
fn MD5_5 ()
{
let mut md5 = MD5::New ();
md5.AddInput ("12345678901234567890123456789012345678901234567890123456789012345678901234567890".as_bytes ());
let output = md5.GetOutput ();
assert! (hex::encode (output) == "57edf4a22be3c955ac49da2e2107b67a");
}
#[test]
fn MD5_6 ()
{
let mut md5 = MD5::New ();
md5.AddInput ("The quick brown fox jumps over the lazy dog".as_bytes ());
let output = md5.GetOutput ();
assert! (hex::encode (output) == "9e107d9d372bb6826bd81d3542a419d6");
}
}