diff --git a/src/main.rs b/src/main.rs index a345f81..9be7b79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,9 +47,8 @@ fn main() { set4::challenge28(); set4::challenge29(); set4::challenge30(); + set4::challenge31(); } else { - set4::challenge28(); - set4::challenge29(); - set4::challenge30(); + set4::challenge31(); } } diff --git a/src/md4.rs b/src/md4.rs index 0d823b2..14c0d6b 100644 --- a/src/md4.rs +++ b/src/md4.rs @@ -1,95 +1,160 @@ // MD4 implementation based on https://docs.rs/md4/latest/md4/ use crate::bytes::Bytes; +const BLOCK_BYTES: usize = 64; -// #[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") +pub struct Md4Core { + byte_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, byte_len: 0 } + } +} +impl Md4Core { + fn update(&mut self, bytes: &[u8; BLOCK_BYTES]) { + compress(&mut self.state, bytes); + self.byte_len += BLOCK_BYTES as u64; + } + + pub fn get_padding(&self, buffer: &[u8]) -> Vec { + let bit_len = self + .byte_len + .wrapping_add(buffer.len() as u64) + .wrapping_mul(8); + + let mut padding = vec![]; + padding.push(0x80); + while (buffer.len() + padding.len()) % BLOCK_BYTES != (BLOCK_BYTES - 8) { + padding.push(0x0); + } + padding.append(&mut (bit_len as u32).to_le_bytes().to_vec()); + padding.append(&mut ((bit_len >> 32) as u32).to_le_bytes().to_vec()); + padding + } + + fn finalize(&mut self, mut buffer: Vec) -> Bytes { + buffer.append(&mut self.get_padding(&buffer)); + for bytes in buffer.chunks(BLOCK_BYTES) { + compress(&mut self.state, &bytes.try_into().unwrap()); + } + + Bytes( + self.state + .iter() + .map(|i| i.to_le_bytes()) + .flatten() + .collect(), + ) + } + + pub fn hash(&mut self, bytes: &Bytes) -> Bytes { + let mut final_bytes = vec![]; + for bytes in bytes.0.chunks(BLOCK_BYTES) { + if bytes.len() == BLOCK_BYTES { + self.update(&bytes.try_into().unwrap()); + } else { + final_bytes = bytes.to_vec(); + } + } + self.finalize(final_bytes) + } + + pub fn fix(&mut self, fixate: [u32; 4], byte_len: u64) { + for i in 0..4 { + self.state[i] = fixate[i]; + } + self.byte_len = byte_len; + } +} + +fn compress(state: &mut [u32; 4], input: &[u8; BLOCK_BYTES]) { + 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 { + let mut md4 = Md4Core::default(); + md4.hash(bytes) +} + +pub fn authenticate(message: &Bytes, key: &Bytes) -> Bytes { + let mut c = vec![]; + c.append(&mut key.0.to_vec()); + c.append(&mut message.0.to_vec()); + hash(&Bytes(c)) +} + +pub fn verify(message: &Bytes, key: &Bytes, mac: &Bytes) -> bool { + return authenticate(&message, &key) == *mac; } diff --git a/src/set4.rs b/src/set4.rs index 918a137..f41fff6 100644 --- a/src/set4.rs +++ b/src/set4.rs @@ -228,46 +228,80 @@ pub fn challenge29() { } assert!(sha1::verify(&Bytes(forged_message), &key, &mac_forged)); - println!("[okay] Challenge 29: forged SHA-1 keyed MAC successfully"); + println!("[okay] Challenge 29: extended SHA-1 keyed message successfully"); } 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. - + // test MD4 implementation 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"); + 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("abcdefghijklmnopqrstuvwxyz")), + Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"), + ); + assert_eq!( + md4::hash(&Bytes(vec![b'a'; 1337])), + Bytes::from_hex("9a4bceae0ae389c4653ad92cfd7bfc3e"), + ); + + // extend MD4 copy and pasted from SHA-1 #allow!(dont-repeat-yourself) + fn hash_fixated(bytes: &Bytes, fixture: &Bytes, byte_len: u64) -> Bytes { + let mut m = md4::Md4Core::default(); + let fixate: Vec = fixture + .0 + .chunks(4) + .map(|c| u32::from_le_bytes(c.try_into().unwrap())) + .collect(); + m.fix(fixate.try_into().unwrap(), byte_len); + m.hash(&bytes) + } + + let key = Bytes::random_range(2, 64); + let message = Bytes::from_utf8( + "comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon", + ); + let mac = md4::authenticate(&message, &key); + assert!(md4::verify(&message, &key, &mac)); + + let mut forged_message = vec![]; + let mut mac_forged = Bytes(vec![]); + for key_len in 1..128 { + // get padding for key || orig-message + let key_guessed = vec![b'z'; key_len]; // key-guessed + let mut bytes = key_guessed.to_vec(); + bytes.append(&mut message.0.to_vec()); // original-message + let md4 = md4::Md4Core::default(); + let glue_padding = md4.get_padding(&bytes); // glue-padding + + // forget MAC via fixture: make sure to fix md4.state *and* md4.byte_length + let byte_length = (key_guessed.len() + message.len() + glue_padding.len()) as u64; + let new_message = b"admin=true".to_vec(); // new-message + mac_forged = hash_fixated(&Bytes(new_message.to_vec()), &mac, byte_length); + + // forge message: original-message || glue-padding || new-message + forged_message = message.0.to_vec(); + forged_message.append(&mut glue_padding.to_vec()); + forged_message.append(&mut new_message.to_vec()); + let r = md4::verify(&Bytes(forged_message.to_vec()), &key, &mac_forged); + if r { + break; + } + } + + assert!(md4::verify(&Bytes(forged_message), &key, &mac_forged)); + println!("[okay] Challenge 30: implemented and extended MD4 successfully"); +} + +pub fn challenge31() { + println!("[xxxx] Challenge 31: tbd"); } diff --git a/src/sha1.rs b/src/sha1.rs index b3890f5..3270278 100644 --- a/src/sha1.rs +++ b/src/sha1.rs @@ -16,7 +16,13 @@ impl Default for Sha1 { #[inline] fn default() -> Self { Self { - h: [ 0x6745_2301, 0xEFCD_AB89, 0x98BA_DCFE, 0x1032_5476, 0xC3D2_E1F0, ], + h: [ + 0x6745_2301, + 0xEFCD_AB89, + 0x98BA_DCFE, + 0x1032_5476, + 0xC3D2_E1F0, + ], byte_len: 0, } } @@ -44,46 +50,51 @@ fn blk(block: &Block, i: usize) -> u32 { } 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)); + *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: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z = z.wrapping_add((*w & (x ^ y)) ^ y) - .wrapping_add(block[i]) - .wrapping_add(0x5a82_7999) - .wrapping_add(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: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z = z.wrapping_add(*w ^ x ^ y) - .wrapping_add(block[i]) - .wrapping_add(0x6ed9_eba1) - .wrapping_add(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: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z = z.wrapping_add(((*w | x) & y) | (*w & x)) - .wrapping_add(block[i]) - .wrapping_add(0x8f1b_bcdc) - .wrapping_add(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: u32, w: &mut u32, x: u32, y: u32, z: &mut u32, i: usize) { block[i] = blk(block, i); - *z = z.wrapping_add(*w ^ x ^ y) - .wrapping_add(block[i]) - .wrapping_add(0xca62_c1d6) - .wrapping_add(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); } @@ -191,11 +202,11 @@ impl Sha1 { r4(block, b, &mut c, d, e, &mut a, 15); /* Add the working vars back into digest[] */ - self.h[0] += a; - self.h[1] += b; - self.h[2] += c; - self.h[3] += d; - self.h[4] += e; + self.h[0] = self.h[0].wrapping_add(a); + self.h[1] = self.h[1].wrapping_add(b); + self.h[2] = self.h[2].wrapping_add(c); + self.h[3] = self.h[3].wrapping_add(d); + self.h[4] = self.h[4].wrapping_add(e); /* Count the number of transformations */ self.byte_len += BLOCK_BYTES as u64; @@ -219,9 +230,9 @@ impl Sha1 { } // append the original message length as a 64-bit big-endian integer - let bits: u64 = (self.byte_len + bytes.len() as u64) as u64 * 8; - padding.append(&mut ((bits >> 32) as u32).to_be_bytes().to_vec()); - padding.append(&mut (bits as u32).to_be_bytes().to_vec()); + let bit_len: u64 = (self.byte_len + bytes.len() as u64) as u64 * 8; + padding.append(&mut ((bit_len >> 32) as u32).to_be_bytes().to_vec()); + padding.append(&mut (bit_len as u32).to_be_bytes().to_vec()); padding }