From f6b4c98826f3d51f479cccef5d70c3289846d6ea Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Sat, 25 Jun 2022 18:28:38 -0400 Subject: [PATCH] Finish challenge 14. --- src/bytes.rs | 8 ++++ src/main.rs | 26 ++++++------ src/set2.rs | 118 +++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 121 insertions(+), 31 deletions(-) diff --git a/src/bytes.rs b/src/bytes.rs index 6cb3895..bdc20df 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -6,6 +6,10 @@ use std::fmt::Write; // need to import this trait pub struct Bytes(pub Vec); impl Bytes { + pub fn empty() -> Bytes { + Bytes(vec![]) + } + pub fn from_utf8(s: &str) -> Bytes { Bytes(s.as_bytes().iter().map(|c| c.clone()).collect()) } @@ -16,6 +20,10 @@ impl Bytes { String::from(std::str::from_utf8(&v).unwrap()) } + pub fn len(&self) -> usize { + self.0.len() + } + pub fn get_block(&self, block_index: usize, block_size: usize) -> Bytes { Bytes(self.0[(block_index * block_size)..(block_index + 1) * block_size].to_vec()) } diff --git a/src/main.rs b/src/main.rs index 00fdcda..c5ed17b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,18 +7,18 @@ mod set1; mod set2; fn main() { - // set1::challenge1(); - // set1::challenge2(); - // set1::challenge3(); - // set1::challenge4(); - // set1::challenge5(); - // set1::challenge6(); - // set1::challenge7(); - // set1::challenge8(); - // set2::challenge9(); - // set2::challenge10(); - // set2::challenge11(); - // set2::challenge12(); - // set2::challenge13(); + set1::challenge1(); + set1::challenge2(); + set1::challenge3(); + set1::challenge4(); + set1::challenge5(); + set1::challenge6(); + set1::challenge7(); + set1::challenge8(); + set2::challenge9(); + set2::challenge10(); + set2::challenge11(); + set2::challenge12(); + set2::challenge13(); set2::challenge14(); } diff --git a/src/set2.rs b/src/set2.rs index 3c1c4f5..11d6f0b 100644 --- a/src/set2.rs +++ b/src/set2.rs @@ -183,17 +183,17 @@ pub fn challenge12() { let key = Bytes::random(16); // consistent but unknown key assert_eq!(get_block_size(&key), 16); // 1. discover block size assert_eq!(is_encryption_ecb(&key), true); // 2. confirm oracle uses ecb - let rountrip_text = decode(&key); // 3.-6. + let roundtrip_text = decode(&key); // 3.-6. let clear_text = read("data/12.txt"); // 138 (instead of 139); I think we get one additional byte because we guess // the first padding byte. The right approach would be to remove the last // byte, encrypt it, and then compare it to the result of the encryption // oracle, but this approach is fine too. - assert_eq!(rountrip_text.0[..138], clear_text.0); + assert_eq!(roundtrip_text.0[..138], clear_text.0); println!( "[okay] Challenge 12: {}", - rountrip_text.to_utf8()[..17].to_string() + roundtrip_text.to_utf8()[..17].to_string() ); } @@ -284,27 +284,109 @@ pub fn challenge14() { for i in 1..10 { let block_size = i * 16; - let chunks: Vec<&[u8]> = cipher.0.chunks(block_size).collect(); - for i in 0..chunks.len() { - for j in (i + 1)..chunks.len() { - if chunks[i] == chunks[j] { - return block_size; - } + match get_duplicated_block_indices(&cipher, block_size) { + Some((_, _)) => { + return block_size; } + _ => (), } } 0 } + fn get_duplicated_block_indices(cipher: &Bytes, block_size: usize) -> Option<(usize, usize)> { + let chunks: Vec<&[u8]> = cipher.0.chunks(block_size).collect(); + for i in 0..chunks.len() { + for j in (i + 1)..chunks.len() { + if chunks[i] == chunks[j] { + return Some((i, j)); + } + } + } + None + } + + fn get_prefix_size(prefix: &Bytes, key: &Bytes) -> usize { + let block_size = get_block_size(&prefix, &key); + let duplicated_text = Bytes::from_utf8("aaaabbbbccccddddaaaabbbbccccdddd").0; + + for i in 0..block_size { + let mut padding = vec![b'a'; i]; + padding.append(&mut duplicated_text.to_vec()); + let cipher = encryption_oracle(prefix, key, &Bytes(padding)); + match get_duplicated_block_indices(&cipher, block_size) { + Some((first_block, _)) => { + return block_size * first_block - i; + } + _ => (), + } + } + 0 + } + + fn decode(prefix: &Bytes, key: &Bytes) -> Bytes { + let block_size = get_block_size(prefix, key); + let prefix_size = get_prefix_size(prefix, key); + let prefix_padding_size = block_size - (prefix_size % block_size); + let prefix_padding = Bytes(vec![b'a'; prefix_padding_size]); + let block_count = encryption_oracle(&prefix, &key, &prefix_padding).len() / block_size; + let first_block_index = (prefix_size + prefix_padding_size) / block_size; + let mut clear_text = vec![]; + + // AES-128-ECB(random-prefix || attacker-controlled || target-bytes, random-key) + // rrrrrrrrrrrrrrrrrrrpppppppppppppaaaaaaaaaaaaaaathe real text + // 0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f + // 0 1 ^ 2 4 5 + // | ^ first_block_index + // \ prefix_padding + for block_index in first_block_index..block_count { + let mut clear_text_block = vec![]; + for padding_length in (0..block_size).rev() { + let full_padding_text = vec![b'-'; prefix_padding_size + padding_length]; + let expected = encryption_oracle(prefix, key, &Bytes(full_padding_text.to_vec())); + let expected_block = expected.get_block(block_index, block_size); + + let mut known_text = if block_index == first_block_index { + full_padding_text.to_vec() + } else { + let mut prefix_padding = vec![b'-'; prefix_padding_size]; + let clear_text_offset = ((block_index - first_block_index - 1) * block_size) + + (block_size - padding_length); + prefix_padding.append( + &mut clear_text[clear_text_offset..(clear_text_offset + padding_length)] + .to_vec(), + ); + prefix_padding + }; + known_text.append(&mut clear_text_block.to_vec()); + + for i in 0..255 { + let mut guess_text = known_text.to_vec(); + guess_text.push(i); + let cipher_block = encryption_oracle(prefix, key, &Bytes(guess_text)) + .get_block(first_block_index, block_size); + if cipher_block.0 == expected_block.0 { + clear_text_block.push(i); + break; + } + } + } + clear_text.append(&mut clear_text_block); + } + // We get last byte from cbs padding so remove it. + Bytes(clear_text[0..(clear_text.len() - 1)].to_vec()) + } + let prefix = Bytes::random_range(0, 200); - let key = Bytes::random(16); // consistent but unknown key + let key = Bytes::random(16); assert_eq!(get_block_size(&prefix, &key), 16); - let _clear_text = read("data/12.txt"); - - // AES-128-ECB(random-prefix || attacker-controlled || target-bytes, random-key) - // rrrrrrrrrrrrrrrrrrraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaathe real text - // 0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f - // 0 1 2 4 5 - - println!("[xxxx] Challenge 14: {}", get_block_size(&prefix, &key)); + let prefix_len = get_prefix_size(&prefix, &key); + assert_eq!(prefix.len(), prefix_len); + let roundtrip_text = decode(&prefix, &key); + let clear_text = read("data/12.txt"); + assert_eq!(roundtrip_text, clear_text); + println!( + "[okay] Challenge 14: {}", + roundtrip_text.to_utf8()[..17].to_string() + ); }