lore/src/hash/md4.rs

240 lines
9 KiB
Rust
Raw Normal View History

2022-09-16 12:07:57 +00:00
use crate::hash::{bytes_to_words_le, words_to_bytes_le, Digest};
// based on RFC1320
const A: u32 = 0x67452301;
const B: u32 = 0xefcdab89;
const C: u32 = 0x98badcfe;
const D: u32 = 0x10325476;
// additional constants for round 1, 2 & 3
const C1: u32 = 0;
const C2: u32 = 0x5a827999;
const C3: u32 = 0x6ed9eba1;
// shifts & indices for each step
const S: [u32; 48] = [
3, 7, 11, 19, 3, 7, 11, 19, 3, 7, 11, 19, 3, 7, 11, 19, 3, 5, 9, 13, 3, 5, 9, 13, 3, 5, 9, 13,
3, 5, 9, 13, 3, 9, 11, 15, 3, 9, 11, 15, 3, 9, 11, 15, 3, 9, 11, 15,
];
const W: [usize; 48] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14,
3, 7, 11, 15, 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15,
];
// round functions
const F: fn(u32, u32, u32) -> u32 = |x, y, z| (x & y) | (!x & z);
const G: fn(u32, u32, u32) -> u32 = |x: u32, y: u32, z: u32| (x & y) | (x & z) | (y & z);
const H: fn(u32, u32, u32) -> u32 = |x: u32, y: u32, z: u32| x ^ y ^ z;
// pad the message to next 512-bit interval
pub(crate) fn pad(message: impl AsRef<[u8]>) -> Vec<u8> {
let mut message = message.as_ref().to_vec();
let message_length = message.len().wrapping_mul(8) as u64;
// add 1 bit (le)
message.push(0x80);
// add 0 bits until length in bits is congruent to 448 mod 512
while (message.len()) % 64 != 56 {
message.push(0u8)
}
// append message length (64 bits)
message.extend(message_length.to_le_bytes());
message
}
// compute an invidiual step in the md4 algorithm
fn step([mut a, b, c, d]: [u32; 4], words: &[u32], index: usize) -> [u32; 4] {
// choose function and constant based on which round is currently active
let (rc, f) = match index {
0..=15 => (C1, F),
16..=31 => (C2, G),
32..=47 => (C3, H),
_ => panic!("This function shouldn't be called using an index outside 0..48"),
};
// main operation
a = f(b, c, d)
.wrapping_add(a)
.wrapping_add(words[W[index]])
.wrapping_add(rc)
.rotate_left(S[index]);
[a, b, c, d]
}
/// Compute the MD4 hash of the input bytes
/// # Examples
/// ```
/// let input = "abc";
/// let digest = lore::md4(input);
///
/// assert_eq!(digest.to_string(), "a448017aaf21d8525fc10ae87aa6729d");
/// ```
pub fn hash(message: impl AsRef<[u8]>) -> Digest<16> {
let padded = pad(message);
let buffer = padded.array_chunks::<64>().map(bytes_to_words_le).fold(
[A, B, C, D],
|[a, b, c, d], words| {
// perform rounds on this chunk of data
let mut state = [a, b, c, d];
for i in 0..48 {
state = step(state, &words, i);
state.rotate_right(1);
}
[
a.wrapping_add(state[0]),
b.wrapping_add(state[1]),
c.wrapping_add(state[2]),
d.wrapping_add(state[3]),
]
},
);
let digest = *words_to_bytes_le(buffer)
.array_chunks::<16>()
.next()
.unwrap();
Digest(digest)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn md4_pad() {
assert_eq!(pad([1u8]).len() % 64, 0);
assert_eq!(pad([1u8; 63]).len() % 64, 0);
assert_eq!(pad([1u8; 65]).len() % 64, 0);
assert_eq!(pad([1u8; 511]).len() % 64, 0);
assert_eq!(pad([1u8; 512]).len() % 64, 0);
assert_eq!(pad([1u8; 513]).len() % 64, 0);
assert_eq!(pad([1u8; 4472]).len() % 64, 0);
assert_eq!(
vec![
0x01, 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,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
],
pad([1u8])
);
}
#[test]
fn md4_hash() {
assert_eq!(
"1bee69a46ba811185c194762abaeae90",
hash("The quick brown fox jumps over the lazy dog").to_string()
);
assert_eq!(
"b86e130ce7028da59e672d56ad0113df",
hash("The quick brown fox jumps over the lazy cog").to_string()
);
assert_eq!("31d6cfe0d16ae931b73c59d7e0c089c0", hash("").to_string());
// RFC 1320 test suite
assert_eq!(hash("").to_string(), "31d6cfe0d16ae931b73c59d7e0c089c0");
assert_eq!(hash("a").to_string(), "bde52cb31de33e46245e05fbdbd6fb24");
assert_eq!(hash("abc").to_string(), "a448017aaf21d8525fc10ae87aa6729d");
assert_eq!(
hash("message digest").to_string(),
"d9130a8164549fe818874806e1c7014b"
);
assert_eq!(
hash("abcdefghijklmnopqrstuvwxyz").to_string(),
"d79e1c308aa5bbcdeea8ed63df412da9"
);
assert_eq!(
hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").to_string(),
"043f8582f241db351ce627e153e7f0e4"
);
assert_eq!(
hash(
"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
)
.to_string(),
"e33b4ddc9c38f2199c3e7b164fcc0536"
);
assert_eq!(
hash("Rosetta Code").to_string(),
"a52bcfc6a0d0d300cdc5ddbfbefe478b"
)
}
#[test]
fn md4_steps() {
let expected: [[u32; 4]; 48] = [
[0x2b9b7a8b, 0xefcdab89, 0x98badcfe, 0x10325476],
[0x1ebbf3f6, 0x2b9b7a8b, 0xefcdab89, 0x98badcfe],
[0xf636674f, 0x1ebbf3f6, 0x2b9b7a8b, 0xefcdab89],
[0x3e787c49, 0xf636674f, 0x1ebbf3f6, 0x2b9b7a8b],
[0x127b1453, 0x3e787c49, 0xf636674f, 0x1ebbf3f6],
[0x9c35a18a, 0x127b1453, 0x3e787c49, 0xf636674f],
[0x7e1c9145, 0x9c35a18a, 0x127b1453, 0x3e787c49],
[0x0adad780, 0x7e1c9145, 0x9c35a18a, 0x127b1453],
[0x85c62aed, 0x0adad780, 0x7e1c9145, 0x9c35a18a],
[0x881a850b, 0x85c62aed, 0x0adad780, 0x7e1c9145],
[0xf71e7006, 0x881a850b, 0x85c62aed, 0x0adad780],
[0x135c5da7, 0xf71e7006, 0x881a850b, 0x85c62aed],
[0x0727d7d9, 0x135c5da7, 0xf71e7006, 0x881a850b],
[0x9b7d493d, 0x0727d7d9, 0x135c5da7, 0xf71e7006],
[0x1e300fd2, 0x9b7d493d, 0x0727d7d9, 0x135c5da7],
[0xb60174a1, 0x1e300fd2, 0x9b7d493d, 0x0727d7d9],
[0x2a7873ab, 0xb60174a1, 0x1e300fd2, 0x9b7d493d],
[0x86074f26, 0x2a7873ab, 0xb60174a1, 0x1e300fd2],
[0x68021c3d, 0x86074f26, 0x2a7873ab, 0xb60174a1],
[0xc9ad2750, 0x68021c3d, 0x86074f26, 0x2a7873ab],
[0x6b1b8763, 0xc9ad2750, 0x68021c3d, 0x86074f26],
[0x329a0609, 0x6b1b8763, 0xc9ad2750, 0x68021c3d],
[0x3f3a2e5c, 0x329a0609, 0x6b1b8763, 0xc9ad2750],
[0x34e64be9, 0x3f3a2e5c, 0x329a0609, 0x6b1b8763],
[0x0de3f443, 0x34e64be9, 0x3f3a2e5c, 0x329a0609],
[0x5fddbd79, 0x0de3f443, 0x34e64be9, 0x3f3a2e5c],
[0x494abd6f, 0x5fddbd79, 0x0de3f443, 0x34e64be9],
[0x9069bba6, 0x494abd6f, 0x5fddbd79, 0x0de3f443],
[0x0d815e5e, 0x9069bba6, 0x494abd6f, 0x5fddbd79],
[0x753ed018, 0x0d815e5e, 0x9069bba6, 0x494abd6f],
[0xee224d71, 0x753ed018, 0x0d815e5e, 0x9069bba6],
[0xd232eb01, 0xee224d71, 0x753ed018, 0x0d815e5e],
[0x57e97dc9, 0xd232eb01, 0xee224d71, 0x753ed018],
[0x252ee4a0, 0x57e97dc9, 0xd232eb01, 0xee224d71],
[0x8d5bd7ef, 0x252ee4a0, 0x57e97dc9, 0xd232eb01],
[0x92942054, 0x8d5bd7ef, 0x252ee4a0, 0x57e97dc9],
[0x38475e43, 0x92942054, 0x8d5bd7ef, 0x252ee4a0],
[0x22f47377, 0x38475e43, 0x92942054, 0x8d5bd7ef],
[0xe6878422, 0x22f47377, 0x38475e43, 0x92942054],
[0x5ab5fed1, 0xe6878422, 0x22f47377, 0x38475e43],
[0x32463ee3, 0x5ab5fed1, 0xe6878422, 0x22f47377],
[0x85465040, 0x32463ee3, 0x5ab5fed1, 0xe6878422],
[0xb801aa18, 0x85465040, 0x32463ee3, 0x5ab5fed1],
[0xd796ec48, 0xb801aa18, 0x85465040, 0x32463ee3],
[0x5f8a08a4, 0xd796ec48, 0xb801aa18, 0x85465040],
[0x7b15aa48, 0x5f8a08a4, 0xd796ec48, 0xb801aa18],
[0x2722e8cf, 0x7b15aa48, 0x5f8a08a4, 0xd796ec48],
[0x11062517, 0x2722e8cf, 0x7b15aa48, 0x5f8a08a4],
];
// perform first step of md4 round 1
let words: [u32; 16] = [
0x65736f52, 0x20617474, 0x65646f43, 0x00000080, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000060, 0x00000000,
];
let mut state: [u32; 4] = [A, B, C, D];
#[allow(clippy::needless_range_loop)]
for i in 0..48 {
state = step(state, &words, i);
assert_eq!(expected[i], state);
state.rotate_right(1);
}
}
}