Detect ECB via duplicated chunks instead of Hemming distance as I should have done in the first place.
This commit is contained in:
14
src/bytes.rs
14
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<u32> = Iterator::zip(a.iter(), b.iter())
|
||||
|
||||
13
src/set1.rs
13
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<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);
|
||||
}
|
||||
|
||||
31
src/set2.rs
31
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 {
|
||||
|
||||
Reference in New Issue
Block a user