Detect ECB via duplicated chunks instead of Hemming distance as I should have done in the first place.

This commit is contained in:
2022-04-27 10:34:40 -04:00
parent 06af500a52
commit 994da471c4
3 changed files with 35 additions and 23 deletions

View File

@@ -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<u32> = Iterator::zip(a.iter(), b.iter())

View File

@@ -193,17 +193,28 @@ pub fn challenge8() {
rating
}
let expected_index: usize = 132;
let bytes_vector = read("data/8.txt");
let ratings: Vec<u32> = 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::<u32>() 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<usize> = bytes_vector
.iter()
.enumerate()
.filter(|&(_, bytes)| bytes.has_duplicated_cycle(16))
.map(|(i, _)| i)
.collect();
assert_eq!(ix[0], expected_index);
}

View File

@@ -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 {