From c12b1c45a672181e33ac4211764c7e0af30e8fbe Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Fri, 19 Aug 2022 18:39:29 -0400 Subject: [PATCH] Finish challenge 29 - I didn't find it trivial tbh --- {src => data}/set3c19.py | 0 src/main.rs | 5 +- src/set4.rs | 97 ++++++++++++++++++++++++++--------- src/sha1.rs | 108 +++++++++++++++++++++++---------------- 4 files changed, 139 insertions(+), 71 deletions(-) rename {src => data}/set3c19.py (100%) diff --git a/src/set3c19.py b/data/set3c19.py similarity index 100% rename from src/set3c19.py rename to data/set3c19.py diff --git a/src/main.rs b/src/main.rs index 6ade3e1..b6da2b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ mod sha1; mod utils; fn main() { - const RUN_ALL: bool = true; + const RUN_ALL: bool = false; if RUN_ALL { set1::challenge1(); set1::challenge2(); @@ -45,7 +45,8 @@ fn main() { set4::challenge27(); set4::challenge28(); set4::challenge29(); + set4::challenge30(); } else { - set4::challenge29(); + set4::challenge30(); } } diff --git a/src/set4.rs b/src/set4.rs index e6639e5..9940583 100644 --- a/src/set4.rs +++ b/src/set4.rs @@ -1,11 +1,9 @@ -#![allow(arithmetic_overflow)] - -use crate::{bytes::Bytes, cbc, ctr, parser, sha1, utils}; +use crate::{bytes::Bytes, cbc, ctr, ecb, parser, sha1, utils}; pub fn challenge25() { let cipher = utils::read_base64("data/25.txt"); let key = Bytes::from_utf8("YELLOW SUBMARINE"); - let plaintext = crate::ecb::decrypt(&key, &cipher); + let plaintext = ecb::decrypt(&key, &cipher); let key = Bytes::random(16); let nonce: u64 = 0; // otherwise edit would require the nonce too? @@ -32,8 +30,8 @@ pub fn challenge25() { let ciphertext = ctr::encrypt(&key, nonce, &plaintext); let newtext = vec![b'a'; ciphertext.len()]; let cipher_newtext = edit(&ciphertext, &key, 0, &newtext); - let keystream = crate::utils::xor(&newtext, &cipher_newtext.0); - let recovered_plaintext = Bytes(crate::utils::xor(&keystream, &ciphertext.0)); + let keystream = utils::xor(&newtext, &cipher_newtext.0); + let recovered_plaintext = Bytes(utils::xor(&keystream, &ciphertext.0)); assert_eq!(plaintext, recovered_plaintext); println!("[okay] Challenge 25: recovered AES CTR plaintext via edit"); @@ -140,50 +138,99 @@ 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); + let o1 = sha1.hash(&i1); assert_eq!(e1, o1); + sha1.reset(); let i2 = Bytes(vec![b'a'; 128]); let e2 = Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0"); - let o2 = sha1::hash(&i2); + let o2 = sha1.hash(&i2); assert_eq!(e2, o2); + sha1.reset(); let i3 = Bytes(vec![b'a'; 3]); let e3 = Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815"); - let o3 = sha1::hash(&i3); + let o3 = sha1.hash(&i3); assert_eq!(e3, o3); + sha1.reset(); let i4 = Bytes(vec![]); let e4 = Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709"); - let o4 = sha1::hash(&i4); + let o4 = sha1.hash(&i4); assert_eq!(e4, o4); - fn authenticate(message: &Bytes, key: &Bytes) -> Bytes { - // Write a function to authenticate a message under a secret key by using a - // secret-prefix MAC, which is simply: - // SHA1(key || message) - let mut c = vec![]; - c.append(&mut key.0.to_vec()); - c.append(&mut message.0.to_vec()); - // how to concatenate better: https://stackoverflow.com/a/56490417 - sha1::hash(&Bytes(c)) - } - // 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 // secret key. let mut message = Bytes::from_utf8("love, love, love"); let key = Bytes::from_utf8("kisses!"); - let auth = authenticate(&message, &key); + let mac = sha1::authenticate(&message, &key); message.flip_bit(2, 3); - let auth_tempered = authenticate(&message, &key); - assert!(auth != auth_tempered); + assert!(!sha1::verify(&message, &key, &mac)); println!("[okay] Challenge 28: implemented SHA-1"); } pub fn challenge29() { - println!("[xxxx] Challenge 29: TBD"); + fn hash_fixated(bytes: &Bytes, fixture: &Bytes, byte_len: u64) -> Bytes { + // Now, take the SHA-1 secret-prefix MAC of the message you want to forge --- this is just + // a SHA-1 hash --- and break it into 32 bit SHA-1 registers (SHA-1 calls them "a", "b", + // "c", &c). + let mut s = sha1::Sha1::default(); + let fixate: Vec = fixture + .0 + .chunks(4) + .map(|c| u32::from_be_bytes(c.try_into().unwrap())) + .collect(); + + // Modify your SHA-1 implementation so that callers can pass in new + // values for "a", "b", "c" &c (they normally start at magic numbers). + // With the registers "fixated", hash the additional data you want to + // forge. + s.fix(fixate.try_into().unwrap(), byte_len); + s.hash(&bytes) + } + + // use random + 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 = sha1::authenticate(&message, &key); + assert!(sha1::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 s1 = sha1::Sha1::default(); + let glue_padding = s1.get_padding(&bytes); // glue-padding + + // forget MAC via fixture: make sure to fix sha1.h *and* sha1.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 = sha1::verify(&Bytes(forged_message.to_vec()), &key, &mac_forged); + if r { + break; + } + } + + assert!(sha1::verify(&Bytes(forged_message), &key, &mac_forged)); + println!("[okay] Challenge 29: forged SHA-1 keyed MAC successfully"); +} + +pub fn challenge30() { + println!("[xxxx] Challenge 30: tbd"); } diff --git a/src/sha1.rs b/src/sha1.rs index 6ed6e1f..2104040 100644 --- a/src/sha1.rs +++ b/src/sha1.rs @@ -1,5 +1,5 @@ -use crate::bytes::Bytes; /// Sha1 implementation based on https://github.com/vog/sha1/blob/master/sha1.hpp +use crate::bytes::Bytes; use std::num::Wrapping; const STATE_LEN: usize = 5; @@ -8,9 +8,10 @@ const BLOCK_BYTES: usize = BLOCK_INTS * 4; // 64 type Wu32 = Wrapping; type Block = [Wu32; BLOCK_INTS]; +#[derive(Debug)] pub struct Sha1 { h: [Wu32; STATE_LEN], - block_len: u64, + byte_len: u64, } impl Default for Sha1 { @@ -24,11 +25,21 @@ impl Default for Sha1 { Wrapping(0x10325476), Wrapping(0xC3D2E1F0), ], - block_len: 0, + byte_len: 0, } } } +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 + .chunks(4) + .map(|c| Wrapping(u32::from_be_bytes(c.try_into().unwrap()))) + .collect(); + b.try_into().unwrap() +} + fn rol(value: Wu32, bits: usize) -> Wu32 { return (value << bits) | (value >> (32 - bits)); } @@ -71,10 +82,17 @@ fn r4(block: &mut Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32, impl Sha1 { #[inline] - fn _reset(&mut self) { + pub fn reset(&mut self) { *self = Default::default(); } + pub fn fix(&mut self, fixate: [u32; STATE_LEN], byte_len: u64) { + for i in 0..5 { + self.h[i] = Wrapping(fixate[i]); + } + self.byte_len = byte_len; + } + fn transform(&mut self, block: &mut Block) { /* Copy digest[] to working vars */ let mut a = self.h[0]; @@ -173,62 +191,64 @@ impl Sha1 { self.h[4] += e; /* Count the number of transformations */ - self.block_len += 1; + self.byte_len += BLOCK_BYTES as u64; } + fn update(&mut self, bytes: &[u8]) { - if bytes.len() % BLOCK_BYTES != 0 { - panic!("we only support buffers that are a multiples of 64 atm") - } + assert_eq!(bytes.len() % BLOCK_BYTES, 0); let mut block = bytes_to_block(bytes); self.transform(&mut block); } - fn finalize(&mut self, last_chunk: Vec) -> Bytes { - let mut buffer = last_chunk; - let total_bits: u64 = (self.block_len * (BLOCK_BYTES as u64) + buffer.len() as u64) * 8; + pub fn get_padding(&self, bytes: &Vec) -> Vec { + let mut padding = vec![]; - buffer.push(0x80); + // append 0x80 to the message + padding.push(0x80); - let orig_size = buffer.len(); - while buffer.len() < BLOCK_BYTES { - buffer.push(0x0); + // append 0 ≤ k < 64 bytes so that message.len() ≡ 56 (mod 64) + while (bytes.len() + padding.len()) % BLOCK_BYTES != (BLOCK_BYTES - 8) { + padding.push(0x0); } - let mut block = bytes_to_block(&buffer); - if orig_size > (BLOCK_BYTES - 8) as usize { - self.transform(&mut block); - for i in 0..(BLOCK_INTS - 2) { - block[i] = Wrapping(0); + // 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()); + padding + } + + 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); + } else { + final_bytes = bytes.to_vec(); } } - - block[BLOCK_INTS - 1] = Wrapping(total_bits as u32); - block[BLOCK_INTS - 2] = Wrapping((total_bits >> 32) as u32); - self.transform(&mut block); - + let mut padding = self.get_padding(&final_bytes); + final_bytes.append(&mut padding); + for bytes in final_bytes.chunks(BLOCK_BYTES) { + self.update(&bytes); + } Bytes(self.h.iter().map(|i| i.0.to_be_bytes()).flatten().collect()) } } -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 - .chunks(4) - .map(|c| Wrapping(u32::from_be_bytes(c.try_into().unwrap()))) - .collect(); - b.try_into().unwrap() +pub fn authenticate(message: &Bytes, key: &Bytes) -> Bytes { + // Write a function to authenticate a message under a secret key by using a + // secret-prefix MAC, which is simply: + // SHA1(key || message) + let mut c = vec![]; + c.append(&mut key.0.to_vec()); + c.append(&mut message.0.to_vec()); + // how to concatenate better: https://stackoverflow.com/a/56490417 + let mut sha1 = Sha1::default(); + let r = sha1.hash(&Bytes(c)); + r } -pub fn hash(bytes: &Bytes) -> Bytes { - let mut s = Sha1::default(); - let mut last_chunk = vec![]; - for bytes in bytes.0.chunks(BLOCK_BYTES) { - if bytes.len() == BLOCK_BYTES { - s.update(&bytes); - } else { - last_chunk = bytes.to_vec(); - } - } - s.finalize(last_chunk) +pub fn verify(message: &Bytes, key: &Bytes, mac: &Bytes) -> bool { + return authenticate(&message, &key) == *mac; }