Last active
May 10, 2024 03:18
-
-
Save PsychedelicShayna/8218b8f79144685be368bd2b6183ad9e to your computer and use it in GitHub Desktop.
A condensed, 358 line MD5 implementation for Rust, easy to copy paste into other projects.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pub mod md5 { | |
use std::convert::TryInto; | |
use std::fmt::Display; | |
use std::{fs::File, io::Read}; | |
pub const BLOCK_LENGTH: usize = 64; | |
pub const DIGEST_LENGTH: usize = 16; | |
#[rustfmt::skip] | |
pub const INITIAL_STATE: [u32; 4] = [ | |
0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476 | |
]; | |
#[rustfmt::skip] | |
pub const SHIFTS: [u8; 64] = [ | |
07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, | |
05, 09, 14, 20, 05, 09, 14, 20, 05, 09, 14, 20, 05, 09, 14, 20, | |
04, 11, 16, 23, 04, 11, 16, 23, 04, 11, 16, 23, 04, 11, 16, 23, | |
06, 10, 15, 21, 06, 10, 15, 21, 06, 10, 15, 21, 06, 10, 15, 21 | |
]; | |
#[rustfmt::skip] | |
pub const PADDING: [u8; 64] = [ | |
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |
]; | |
#[rustfmt::skip] | |
pub const T_VALUES: [u32; 64] = [ | |
0xd76a_a478, 0xe8c7_b756, 0x2420_70db, 0xc1bd_ceee, 0xf57c_0faf, | |
0x4787_c62a, 0xa830_4613, 0xfd46_9501, 0x6980_98d8, 0x8b44_f7af, | |
0xffff_5bb1, 0x895c_d7be, 0x6b90_1122, 0xfd98_7193, 0xa679_438e, | |
0x49b4_0821, 0xf61e_2562, 0xc040_b340, 0x265e_5a51, 0xe9b6_c7aa, | |
0xd62f_105d, 0x0244_1453, 0xd8a1_e681, 0xe7d3_fbc8, 0x21e1_cde6, | |
0xc337_07d6, 0xf4d5_0d87, 0x455a_14ed, 0xa9e3_e905, 0xfcef_a3f8, | |
0x676f_02d9, 0x8d2a_4c8a, 0xfffa_3942, 0x8771_f681, 0x6d9d_6122, | |
0xfde5_380c, 0xa4be_ea44, 0x4bde_cfa9, 0xf6bb_4b60, 0xbebf_bc70, | |
0x289b_7ec6, 0xeaa1_27fa, 0xd4ef_3085, 0x0488_1d05, 0xd9d4_d039, | |
0xe6db_99e5, 0x1fa2_7cf8, 0xc4ac_5665, 0xf429_2244, 0x432a_ff97, | |
0xab94_23a7, 0xfc93_a039, 0x655b_59c3, 0x8f0c_cc92, 0xffef_f47d, | |
0x8584_5dd1, 0x6fa8_7e4f, 0xfe2c_e6e0, 0xa301_4314, 0x4e08_11a1, | |
0xf753_7e82, 0xbd3a_f235, 0x2ad7_d2bb, 0xeb86_d391 | |
]; | |
#[rustfmt::skip] | |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
pub struct MD5Params { | |
pub a: u32, pub b: u32, pub c: u32, pub d: u32, | |
pub x: u32, pub s: u8, pub ac: u32, | |
} | |
#[rustfmt::skip] | |
impl MD5Params { | |
pub fn new(a: u32, b: u32, c: u32, d: u32, x: u32, s: u8, ac: u32) -> Self { | |
Self { a, b, c, d, x, s, ac } | |
} | |
} | |
pub fn f(params: &mut MD5Params) -> u32 { | |
let b = params.b; | |
let c = params.c; | |
let d = params.d; | |
let res = mdg_f(b, c, d); | |
params.a = params | |
.a | |
.wrapping_add(res.wrapping_add(params.x.wrapping_add(params.ac))); | |
params.a = rotate_left(params.a, params.s.into()); | |
params.a = params.a.wrapping_add(params.b); | |
b.wrapping_add(res) | |
} | |
pub fn g(params: &mut MD5Params) -> u32 { | |
let b = params.b; | |
let c = params.c; | |
let d = params.d; | |
params.a = params.a.wrapping_add(params.x.wrapping_add(params.ac)); | |
params.a = params.a.wrapping_add(!d & c); | |
params.a = params.a.wrapping_add(d & b); | |
params.a = rotate_left(params.a, params.s.into()); | |
params.a = params.a.wrapping_add(params.b); | |
b.wrapping_add(!d & c) | |
} | |
pub fn h(params: &mut MD5Params) -> u32 { | |
let b = params.b; | |
let c = params.c; | |
let d = params.d; | |
params.a = params.a.wrapping_add(params.x.wrapping_add(params.ac)); | |
params.a = params.a.wrapping_add(b ^ c ^ d); | |
params.a = rotate_left(params.a, params.s.into()); | |
params.a = params.a.wrapping_add(params.b); | |
b.wrapping_add(b ^ c ^ d) | |
} | |
pub fn i(params: &mut MD5Params) -> u32 { | |
let b = params.b; | |
let c = params.c; | |
let d = params.d; | |
params.a = params.a.wrapping_add(params.x.wrapping_add(params.ac)); | |
params.a = params.a.wrapping_add(c ^ (d | !b)); | |
params.a = rotate_left(params.a, params.s.into()); | |
params.a = params.a.wrapping_add(params.b); | |
b.wrapping_add(c ^ (d | !b)) | |
} | |
#[inline(always)] | |
pub fn mdg_f(x: u32, y: u32, z: u32) -> u32 { | |
(x & y) | (!x & z) | |
} | |
#[inline(always)] | |
pub fn mdg_h(x: u32, y: u32, z: u32) -> u32 { | |
x ^ y ^ z | |
} | |
#[inline(always)] | |
pub fn mdg_i(x: u32, y: u32, z: u32) -> u32 { | |
y ^ (x | !z) | |
} | |
#[inline(always)] | |
pub fn rotate_left(x: u32, n: u32) -> u32 { | |
(x << n) | (x >> (32 - n)) | |
} | |
pub trait Digest { | |
fn reset(&mut self) -> &mut Self; | |
fn update(&mut self, value: &[u8]) -> &mut Self; | |
fn update_file(&mut self, path: &str) -> &mut Self; | |
fn hexdigest(value: &str) -> String; | |
fn hexdigest_file(path: &str) -> String; | |
fn reset_file(&mut self, path: &str) -> &mut Self; | |
} | |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
pub struct MD5 { | |
pub buffer: [u8; BLOCK_LENGTH], | |
pub count: [u32; 2], | |
pub digest: [u8; DIGEST_LENGTH], | |
pub state: [u32; 4], | |
} | |
impl MD5 { | |
pub fn finalize(&mut self) -> &Self { | |
let bits: [u8; 8] = (0..8) | |
.into_iter() | |
.map(|i| (self.count[i >> 2] >> ((i & 3) << 3)) as u8) | |
.collect::<Vec<_>>() | |
.try_into() | |
.expect("Couldn't transform vec into array"); | |
let index = (self.count[0] >> 3) & 63; | |
let pad_len = if index < 56 { 56 - index } else { 120 - index }; | |
self.update_with_len(&PADDING, pad_len as usize); | |
self.update(&bits); | |
self.digest = (0..DIGEST_LENGTH) | |
.into_iter() | |
.map(|i| (self.state[i >> 2] >> ((i & 3) << 3)) as u8) | |
.collect::<Vec<_>>() | |
.try_into() | |
.expect("Couldn't transform vec into array"); | |
self | |
} | |
pub fn new() -> Self { | |
Self { | |
state: INITIAL_STATE, | |
count: [0, 0], | |
buffer: [0; BLOCK_LENGTH], | |
digest: [0; DIGEST_LENGTH], | |
} | |
} | |
pub fn transform(&mut self, data: &[u8]) -> &mut Self { | |
const fn f(x: u32, y: u32, z: u32) -> u32 { | |
(x & y) | (!x & z) | |
} | |
const fn g(x: u32, y: u32, z: u32) -> u32 { | |
(x & z) | (y & !z) | |
} | |
const fn h(x: u32, y: u32, z: u32) -> u32 { | |
x ^ y ^ z | |
} | |
const fn i(x: u32, y: u32, z: u32) -> u32 { | |
y ^ (x | !z) | |
} | |
let (mut a, mut b, mut c, mut d): (u32, u32, u32, u32) = | |
(self.state[0], self.state[1], self.state[2], self.state[3]); | |
for (idx, t_value) in T_VALUES.iter().enumerate() { | |
let (value, g): (u32, usize) = match idx { | |
0..=15 => (f(b, c, d), idx), | |
16..=31 => (g(b, c, d), (5 * idx + 1) % DIGEST_LENGTH), | |
32..=47 => (h(b, c, d), (3 * idx + 5) % DIGEST_LENGTH), | |
48..=63 => (i(b, c, d), (7 * idx) % DIGEST_LENGTH), | |
_ => unreachable!(), | |
}; | |
let part_value = u32::from_ne_bytes( | |
data[4 * g..4 * g + 4] | |
.try_into() | |
.expect("Couldn't transform slice into array"), | |
); | |
let f = value | |
.wrapping_add(a) | |
.wrapping_add(*t_value) | |
.wrapping_add(part_value); | |
a = d; | |
d = c; | |
c = b; | |
b = b.wrapping_add(f.rotate_left(SHIFTS[idx].into())); | |
} | |
self.state[0] = self.state[0].wrapping_add(a); | |
self.state[1] = self.state[1].wrapping_add(b); | |
self.state[2] = self.state[2].wrapping_add(c); | |
self.state[3] = self.state[3].wrapping_add(d); | |
self | |
} | |
pub fn update_with_len(&mut self, value: &[u8], nbytes: usize) -> &mut Self { | |
let mut offset = ((self.count[0] >> 3) & 63) as usize; | |
let nbits = (nbytes << 3) as u32; | |
let p = value; | |
if nbytes == 0 { | |
return self; | |
} | |
self.count[0] = self.count[0].wrapping_add(nbits); | |
if self.count[0] < nbits { | |
self.count[1] += 1; | |
} | |
self.count[1] += (nbytes >> 29) as u32; | |
let part_len = BLOCK_LENGTH - offset; | |
let mut i = part_len; | |
if nbytes >= part_len { | |
self.buffer[offset..(offset + part_len)].clone_from_slice(&p[..part_len]); | |
let buf = self.buffer; | |
self.transform(&buf); | |
while i < nbytes - part_len { | |
if nbytes - i >= 64 { | |
let buf = self.buffer[i..i + part_len].to_vec(); | |
self.transform(&buf); | |
i += 64; | |
} else { | |
break; | |
} | |
} | |
offset = 0; | |
} else { | |
i = 0; | |
} | |
self.buffer[offset..(offset + nbytes - i)].clone_from_slice(&p[i..nbytes]); | |
self | |
} | |
} | |
impl Default for MD5 { | |
fn default() -> Self { | |
Self::new() | |
} | |
} | |
impl Display for MD5 { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
for di in self.digest { | |
write!(f, "{di:02x}")?; | |
} | |
Ok(()) | |
} | |
} | |
impl Digest for MD5 { | |
fn reset(&mut self) -> &mut Self { | |
self.state = INITIAL_STATE; | |
self.count.fill(0); | |
self.buffer.fill(0); | |
self.digest.fill(0); | |
self | |
} | |
fn update(&mut self, value: &[u8]) -> &mut Self { | |
self.update_with_len(value, value.len()) | |
} | |
fn update_file(&mut self, path: &str) -> &mut Self { | |
let mut file = File::open(path).expect("Couldn't open file"); | |
let mut buffer = [0; 1024]; | |
loop { | |
let nbytes = file.read(&mut buffer).expect("Couldn't read file"); | |
if nbytes == 0 { | |
break; | |
} | |
self.update_with_len(&buffer, nbytes); | |
} | |
self | |
} | |
fn hexdigest(value: &str) -> String { | |
Self::new().update(value.as_bytes()).finalize().to_string() | |
} | |
fn hexdigest_file(path: &str) -> String { | |
let mut file = File::open(path).expect("Couldn't open file"); | |
let mut buffer = [0; 1024]; | |
let mut mdg = Self::new(); | |
loop { | |
let nbytes = file.read(&mut buffer).expect("Couldn't read file"); | |
if nbytes == 0 { | |
break; | |
} | |
mdg.update_with_len(&buffer, nbytes); | |
} | |
mdg.finalize().to_string() | |
} | |
fn reset_file(&mut self, path: &str) -> &mut Self { | |
self.reset(); | |
self.update_file(path) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Examples on using the module. Credit goes to: https://github.com/sebastienrousseau. | |
// I only condensed and cleaned it up to make it more portable. | |
use md5::*; | |
fn main() { | |
// Example using MD5::hexdigest() for a string input | |
let input = "Hello, world!"; | |
let digest = MD5::hexdigest(input); | |
println!(" MD5::hexdigest() for a string input: {digest}",); | |
// Expected 6cd3556deb0da54bca060b4c39479839 | |
// Example using MD5::hexdigest() for a byte array input | |
let input = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]; // "Hello, world!" | |
let input_str = String::from_utf8(input.to_vec()).unwrap(); | |
let digest = MD5::hexdigest(&input_str); | |
println!(" MD5::hexdigest() for a byte array input:{digest}",); | |
// Expected 6cd3556deb0da54bca060b4c39479839 | |
// Example using MD5::hexdigest() for a file input | |
let digest = MD5::hexdigest_file("mdg/file.txt"); // file.txt contains "Hello, world!" | |
println!(" MD5::hexdigest_file() for a file input: {digest}",); | |
// Expected 6cd3556deb0da54bca060b4c39479839 | |
// Example using MD5::update() for a byte array input | |
let mut mdg = MD5::new(); | |
let input = [ | |
67, 111, 117, 99, | |
111, 117, 44, 32, | |
108, 101, 32, 109, | |
111, 110, 100, 101, | |
33, | |
]; | |
mdg.update(&input); | |
let digest = mdg.finalize(); | |
println!("MD5::update() for a byte array input: {digest}",); | |
// Expected 47353a0e5ed2e1e0d57213a39e9bb7c4 | |
// Example using MD5::update() for a string input | |
let mut mdg = MD5::new(); | |
let input = "Coucou, le monde!"; | |
mdg.update(input.as_bytes()); | |
let digest = mdg.finalize(); | |
println!(" MD5::update() for a string input: {digest}",); | |
// Expected 47353a0e5ed2e1e0d57213a39e9bb7c4 | |
// Example using MD5::update() for a file input | |
let mut mdg = MD5::new(); | |
println!( " MD5::new() is: {}", mdg.finalize()); | |
// Expected d41d8cd98f00b204e9800998ecf8427e | |
mdg.update_file("mdg/update.txt"); | |
let digest = mdg.finalize(); | |
println!(" MD5::update_file() is: {digest}",); | |
// Expected 7fc3e27776139278c6b8e0b6f096b4fb | |
// Example using MD5::reset() for a string input | |
let mut mdg = MD5::new(); | |
println!(" MD5::new() is: {}", mdg.finalize()); | |
// Expected d41d8cd98f00b204e9800998ecf8427e | |
println!(" MD5::reset() for a string input: {}", mdg.reset()); | |
// Expected 00000000000000000000000000000000 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment