EasyCrypto 0.8.1

A simple crypto crate aim at usability
Documentation
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];

		// make M
		for i in (0..input.len ()).step_by (4)
		{
			M[i/4] = Wrapping(u32::from_le_bytes (input[i..i + 4].try_into ().unwrap ()));
		}

		// init values
		let mut a = self.a;
		let mut b = self.b;
		let mut c = self.c;
		let mut d = self.d;

		// do 64 rounds
		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]);
		}

		// add this block hash to current hash
		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
		{
			// not enough byte to process
			self.leftoverInput.extend (input);

			// update input len
			self.inputLen += u64::try_from (input.len ()).unwrap ();
			return;
		}

		// first block
		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 ());

		// do next n blocks
		for block in input[Self::BLOCK_SIZE - self.leftoverInput.len ()..].chunks_exact (Self::BLOCK_SIZE)
		{
			self.AddBlock (block.try_into ().unwrap ());
		}

		// push last block into leftover

		// .......................................
		// |      |      |      |      |      |

		// .......................................
		//     |      |      |      |      |      |

		// .......................................
		//   |      |      |      |      |      |
		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..]);

		// update input len
		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");
	}
}