diff --git a/src/bytes.rs b/src/bytes.rs index 84d7523..36e8e87 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -95,6 +95,20 @@ impl Bytes { ) } + pub fn has_duplicated_cycle(&self, block_size: usize) -> bool { + let Bytes(v) = self; + let chunks: Vec<&[u8]> = v.chunks(block_size).collect(); + // we could use a hashmap to get O(n) instead of O(n^2) + for i in 0..chunks.len() { + for j in (i + 1)..chunks.len() { + if chunks[i] == chunks[j] { + return true; + } + } + } + false + } + #[allow(dead_code)] pub fn hemming(Bytes(a): &Bytes, Bytes(b): &Bytes) -> u32 { let v: Vec = Iterator::zip(a.iter(), b.iter()) diff --git a/src/set1.rs b/src/set1.rs index cd52292..3fbcbc0 100644 --- a/src/set1.rs +++ b/src/set1.rs @@ -193,17 +193,28 @@ pub fn challenge8() { rating } + let expected_index: usize = 132; let bytes_vector = read("data/8.txt"); let ratings: Vec = bytes_vector.iter().map(|b| rate(&b, 16)).collect(); let min_rating = ratings.iter().min().unwrap(); let index = ratings.iter().position(|e| e == min_rating).unwrap(); let average = ratings.iter().sum::() as f32 / ratings.len() as f32; let partial_cipher = bytes_vector[index].to_hex()[..10].to_string(); - if index != 132 { + if index != expected_index { panic!("Regression in challenge 8."); } println!( "[okay] Challenge 8: Cipher {} [{}...] with rating {} (average = {}) is the solution.", index, partial_cipher, min_rating, average ); + + // More elegant solution. + let bytes_vector = read("data/8.txt"); + let ix: Vec = bytes_vector + .iter() + .enumerate() + .filter(|&(_, bytes)| bytes.has_duplicated_cycle(16)) + .map(|(i, _)| i) + .collect(); + assert_eq!(ix[0], expected_index); } diff --git a/src/set2.rs b/src/set2.rs index f7eff3f..ee6cb35 100644 --- a/src/set2.rs +++ b/src/set2.rs @@ -57,11 +57,9 @@ pub fn challenge11() { // Write a function that encrypts data under an unknown key --- that is, a // function that generates a random key and encrypts under it. let key = Bytes(random_bytes(16)); - // Under the hood, have the function append 5-10 bytes (count chosen randomly) // before the plaintext and 5-10 bytes after the plaintext. - let _padded_data = pad_data(data.to_vec()); - let padded_data = Bytes(data.to_vec()); + let padded_data = pad_data(data.to_vec()); // Now, have the function choose to encrypt under ECB 1/2 the time, and under CBC // the other half (just use random IVs each time for CBC). Use rand(2) to decide // which to use. @@ -69,9 +67,8 @@ pub fn challenge11() { let (data, encryption_type) = if zero_or_one == 1 { (ecb::encrypt(&key, &padded_data), EncryptionType::ECB) } else { - // let iv = Bytes(random_bytes(16)); - // (cbc::encrypt(&key, &iv, &padded_data), EncryptionType::CBC) - (Bytes(random_bytes(2876)), EncryptionType::CBC) + let iv = Bytes(random_bytes(16)); + (cbc::encrypt(&key, &iv, &padded_data), EncryptionType::CBC) }; (data, encryption_type) } @@ -80,22 +77,7 @@ pub fn challenge11() { // Detect the block cipher mode the function is using each time. You should end up // with a piece of code that, pointed at a block box that might be encrypting ECB // or CBC, tells you which one is happening. - - fn rate(Bytes(v): &Bytes, size: usize) -> u32 { - let mut rating = 0; - let chunks: Vec<&[u8]> = v.chunks(size).collect(); - let mut count = 0; - for i in 0..chunks.len() { - for j in (i + 1)..chunks.len() { - rating += - Bytes::hemming(&Bytes(chunks[i].to_vec()), &Bytes(chunks[j].to_vec())); - count += 1; - } - } - rating / count - } - let rating = rate(&data, 16); - if rating < 50 { + if data.has_duplicated_cycle(16) { EncryptionType::ECB } else { EncryptionType::CBC @@ -103,6 +85,11 @@ pub fn challenge11() { } fn run_oracle(count: usize) { + // I struggled for a while to understand how the detection oracle can + // work. The issue is that if we provide a random text to encrypt, we + // won't be able to use duplicated blocks as a method for detecting the + // cipher. I don't think there is a way around that, but if we can choose + // the plaintext it becomes trivial. let text = Bytes::from_utf8("aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd"); let mut correct: usize = 0; for _ in 0..count {