diff --git a/src/main.rs b/src/main.rs index b6da2b6..a345f81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod bytes_base64; mod cbc; mod ctr; mod ecb; +mod md4; mod mt19937; mod mtcipher; mod parser; @@ -47,6 +48,8 @@ fn main() { set4::challenge29(); set4::challenge30(); } else { + set4::challenge28(); + set4::challenge29(); set4::challenge30(); } } diff --git a/src/md4.rs b/src/md4.rs new file mode 100644 index 0000000..0d823b2 --- /dev/null +++ b/src/md4.rs @@ -0,0 +1,95 @@ +// MD4 implementation based on https://docs.rs/md4/latest/md4/ +use crate::bytes::Bytes; + +// #[derive(Clone)] +// pub struct Md4Core { +// block_len: u64, +// state: [u32; 4], +// } + +// impl Default for Md4Core { +// #[inline] +// fn default() -> Self { +// let state = [0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476]; +// Self { +// state, +// block_len: 0, +// } +// } +// } + +// fn compress(state: &mut [u32; 4], input: &Block) { +// fn f(x: u32, y: u32, z: u32) -> u32 { +// (x & y) | (!x & z) +// } + +// fn g(x: u32, y: u32, z: u32) -> u32 { +// (x & y) | (x & z) | (y & z) +// } + +// fn h(x: u32, y: u32, z: u32) -> u32 { +// x ^ y ^ z +// } + +// fn op1(a: u32, b: u32, c: u32, d: u32, k: u32, s: u32) -> u32 { +// a.wrapping_add(f(b, c, d)).wrapping_add(k).rotate_left(s) +// } + +// fn op2(a: u32, b: u32, c: u32, d: u32, k: u32, s: u32) -> u32 { +// a.wrapping_add(g(b, c, d)) +// .wrapping_add(k) +// .wrapping_add(0x5A82_7999) +// .rotate_left(s) +// } + +// fn op3(a: u32, b: u32, c: u32, d: u32, k: u32, s: u32) -> u32 { +// a.wrapping_add(h(b, c, d)) +// .wrapping_add(k) +// .wrapping_add(0x6ED9_EBA1) +// .rotate_left(s) +// } + +// let mut a = state[0]; +// let mut b = state[1]; +// let mut c = state[2]; +// let mut d = state[3]; + +// // load block to data +// let mut data = [0u32; 16]; +// for (o, chunk) in data.iter_mut().zip(input.chunks_exact(4)) { +// *o = u32::from_le_bytes(chunk.try_into().unwrap()); +// } + +// // round 1 +// for &i in &[0, 4, 8, 12] { +// a = op1(a, b, c, d, data[i], 3); +// d = op1(d, a, b, c, data[i + 1], 7); +// c = op1(c, d, a, b, data[i + 2], 11); +// b = op1(b, c, d, a, data[i + 3], 19); +// } + +// // round 2 +// for i in 0..4 { +// a = op2(a, b, c, d, data[i], 3); +// d = op2(d, a, b, c, data[i + 4], 5); +// c = op2(c, d, a, b, data[i + 8], 9); +// b = op2(b, c, d, a, data[i + 12], 13); +// } + +// // round 3 +// for &i in &[0, 2, 1, 3] { +// a = op3(a, b, c, d, data[i], 3); +// d = op3(d, a, b, c, data[i + 8], 9); +// c = op3(c, d, a, b, data[i + 4], 11); +// b = op3(b, c, d, a, data[i + 12], 15); +// } + +// state[0] = state[0].wrapping_add(a); +// state[1] = state[1].wrapping_add(b); +// state[2] = state[2].wrapping_add(c); +// state[3] = state[3].wrapping_add(d); +// } + +pub fn hash(_bytes: &Bytes) -> Bytes { + Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0") +} diff --git a/src/set4.rs b/src/set4.rs index 9940583..918a137 100644 --- a/src/set4.rs +++ b/src/set4.rs @@ -1,4 +1,4 @@ -use crate::{bytes::Bytes, cbc, ctr, ecb, parser, sha1, utils}; +use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils}; pub fn challenge25() { let cipher = utils::read_base64("data/25.txt"); @@ -139,28 +139,28 @@ pub fn challenge27() { pub fn challenge28() { let mut sha1 = sha1::Sha1::default(); - let i1 = Bytes(vec![b'a'; 64]); - let e1 = Bytes::from_hex("0098ba824b5c16427bd7a1122a5a442a25ec644d"); - let o1 = sha1.hash(&i1); - assert_eq!(e1, o1); + assert_eq!( + Bytes::from_hex("0098ba824b5c16427bd7a1122a5a442a25ec644d"), + sha1.hash(&Bytes(vec![b'a'; 64])) + ); sha1.reset(); - let i2 = Bytes(vec![b'a'; 128]); - let e2 = Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0"); - let o2 = sha1.hash(&i2); - assert_eq!(e2, o2); + assert_eq!( + Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0"), + sha1.hash(&Bytes(vec![b'a'; 128])) + ); sha1.reset(); - let i3 = Bytes(vec![b'a'; 3]); - let e3 = Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815"); - let o3 = sha1.hash(&i3); - assert_eq!(e3, o3); + assert_eq!( + Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815"), + sha1.hash(&Bytes(vec![b'a'; 3])), + ); sha1.reset(); - let i4 = Bytes(vec![]); - let e4 = Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709"); - let o4 = sha1.hash(&i4); - assert_eq!(e4, o4); + assert_eq!( + Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709"), + sha1.hash(&Bytes(vec![])), + ); // Verify that you cannot tamper with the message without breaking the MAC // you've produced, and that you can't produce a new MAC without knowing the @@ -232,5 +232,42 @@ pub fn challenge29() { } pub fn challenge30() { + // Second verse, same as the first, but use MD4 instead of SHA-1. Having + // done this attack once against SHA-1, the MD4 variant should take much + // less time; mostly just the time you'll spend Googling for an + // implementation of MD4. + + assert_eq!( + md4::hash(&Bytes::from_utf8("")), + Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0"), + ); + // assert_eq!( + // md4::hash(&Bytes::from_utf8("a")), + // Bytes::from_hex("bde52cb31de33e46245e05fbdbd6fb24"), + // ); + // assert_eq!( + // md4::hash(&Bytes::from_utf8("abc")), + // Bytes::from_hex("a448017aaf21d8525fc10ae87aa6729d"), + // ); + // assert_eq!( + // md4::hash(&Bytes::from_utf8("message digest")), + // Bytes::from_hex("d9130a8164549fe818874806e1c7014b"), + // ); + // assert_eq!( + // md4::hash(&Bytes::from_utf8("abcdefghijklmnopqrstuvwxyz")), + // Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"), + // ); + // assert_eq!( + // md4::hash(&Bytes::from_utf8( + // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + // )), + // Bytes::from_hex("043f8582f241db351ce627e153e7f0e4"), + // ); + // assert_eq!( + // md4::hash(&Bytes::from_utf8( + // "12345678901234567890123456789012345678901234567890123456789012345678901234567890" + // )), + // Bytes::from_hex("e33b4ddc9c38f2199c3e7b164fcc0536"), + // ); println!("[xxxx] Challenge 30: tbd"); } diff --git a/src/sha1.rs b/src/sha1.rs index 2104040..b3890f5 100644 --- a/src/sha1.rs +++ b/src/sha1.rs @@ -1,16 +1,14 @@ -/// Sha1 implementation based on https://github.com/vog/sha1/blob/master/sha1.hpp +/// SHA-1 implementation adapted from https://github.com/vog/sha1/blob/master/sha1.hpp use crate::bytes::Bytes; -use std::num::Wrapping; const STATE_LEN: usize = 5; const BLOCK_INTS: usize = 16; // number of 32bit integers per SHA1 block const BLOCK_BYTES: usize = BLOCK_INTS * 4; // 64 -type Wu32 = Wrapping; -type Block = [Wu32; BLOCK_INTS]; +type Block = [u32; BLOCK_INTS]; #[derive(Debug)] pub struct Sha1 { - h: [Wu32; STATE_LEN], + h: [u32; STATE_LEN], byte_len: u64, } @@ -18,13 +16,7 @@ impl Default for Sha1 { #[inline] fn default() -> Self { Self { - h: [ - Wrapping(0x67452301), - Wrapping(0xEFCDAB89), - Wrapping(0x98BADCFE), - Wrapping(0x10325476), - Wrapping(0xC3D2E1F0), - ], + h: [ 0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476, 0xC3D2_E1F0, ], byte_len: 0, } } @@ -33,50 +25,65 @@ impl Default for Sha1 { fn bytes_to_block(bytes: &[u8]) -> Block { assert_eq!(bytes.len(), BLOCK_BYTES); // safety: unwraps work because BLOCK_BYTES length is asserted - let b: Vec = bytes + let b: Vec = bytes .chunks(4) - .map(|c| Wrapping(u32::from_be_bytes(c.try_into().unwrap()))) + .map(|c| u32::from_be_bytes(c.try_into().unwrap())) .collect(); b.try_into().unwrap() } -fn rol(value: Wu32, bits: usize) -> Wu32 { +fn rol(value: u32, bits: usize) -> u32 { return (value << bits) | (value >> (32 - bits)); } -fn blk(block: &Block, i: usize) -> Wu32 { +fn blk(block: &Block, i: usize) -> u32 { return rol( block[(i + 13) & 15] ^ block[(i + 8) & 15] ^ block[(i + 2) & 15] ^ block[i], 1, ); } -fn r0(block: &Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32, i: usize) { - *z = *z + ((*w & (x ^ y)) ^ y) + block[i] + Wrapping(0x5a827999) + rol(v, 5); +fn r0(block: &Block, v: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { + *z = z.wrapping_add((*w & (x ^ y)) ^ y) + .wrapping_add(block[i]) + .wrapping_add(0x5a82_7999) + .wrapping_add(rol(v, 5)); *w = rol(*w, 30); } -fn r1(block: &mut Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32, i: usize) { +fn r1(block: &mut Block, v: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z = *z + ((*w & (x ^ y)) ^ y) + block[i] + Wrapping(0x5a827999) + rol(v, 5); + *z = z.wrapping_add((*w & (x ^ y)) ^ y) + .wrapping_add(block[i]) + .wrapping_add(0x5a82_7999) + .wrapping_add(rol(v, 5)); *w = rol(*w, 30); } -fn r2(block: &mut Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32, i: usize) { +fn r2(block: &mut Block, v: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z += (*w ^ x ^ y) + block[i] + Wrapping(0x6ed9eba1) + rol(v, 5); + *z = z.wrapping_add(*w ^ x ^ y) + .wrapping_add(block[i]) + .wrapping_add(0x6ed9_eba1) + .wrapping_add(rol(v, 5)); *w = rol(*w, 30); } -fn r3(block: &mut Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32, i: usize) { +fn r3(block: &mut Block, v: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z += (((*w | x) & y) | (*w & x)) + block[i] + Wrapping(0x8f1bbcdc) + rol(v, 5); + *z = z.wrapping_add(((*w | x) & y) | (*w & x)) + .wrapping_add(block[i]) + .wrapping_add(0x8f1b_bcdc) + .wrapping_add(rol(v, 5)); *w = rol(*w, 30); } -fn r4(block: &mut Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32, i: usize) { +fn r4(block: &mut Block, v: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z = *z + (*w ^ x ^ y) + block[i] + Wrapping(0xca62c1d6) + rol(v, 5); + *z = z.wrapping_add(*w ^ x ^ y) + .wrapping_add(block[i]) + .wrapping_add(0xca62_c1d6) + .wrapping_add(rol(v, 5)); *w = rol(*w, 30); } @@ -88,7 +95,7 @@ impl Sha1 { pub fn fix(&mut self, fixate: [u32; STATE_LEN], byte_len: u64) { for i in 0..5 { - self.h[i] = Wrapping(fixate[i]); + self.h[i] = fixate[i]; } self.byte_len = byte_len; } @@ -232,7 +239,7 @@ impl Sha1 { for bytes in final_bytes.chunks(BLOCK_BYTES) { self.update(&bytes); } - Bytes(self.h.iter().map(|i| i.0.to_be_bytes()).flatten().collect()) + Bytes(self.h.iter().map(|i| i.to_be_bytes()).flatten().collect()) } }