Skip to content

Instantly share code, notes, and snippets.

@PsychedelicShayna
Last active May 10, 2024 03:18
Show Gist options
  • Save PsychedelicShayna/8218b8f79144685be368bd2b6183ad9e to your computer and use it in GitHub Desktop.
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.
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)
}
}
}
// 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