240 lines
9 KiB
Rust
240 lines
9 KiB
Rust
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|