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)]
|
#[allow(dead_code)]
|
||||||
pub fn hemming(Bytes(a): &Bytes, Bytes(b): &Bytes) -> u32 {
|
pub fn hemming(Bytes(a): &Bytes, Bytes(b): &Bytes) -> u32 {
|
||||||
let v: Vec<u32> = Iterator::zip(a.iter(), b.iter())
|
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
|
rating
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let expected_index: usize = 132;
|
||||||
let bytes_vector = read("data/8.txt");
|
let bytes_vector = read("data/8.txt");
|
||||||
let ratings: Vec<u32> = bytes_vector.iter().map(|b| rate(&b, 16)).collect();
|
let ratings: Vec<u32> = bytes_vector.iter().map(|b| rate(&b, 16)).collect();
|
||||||
let min_rating = ratings.iter().min().unwrap();
|
let min_rating = ratings.iter().min().unwrap();
|
||||||
let index = ratings.iter().position(|e| e == min_rating).unwrap();
|
let index = ratings.iter().position(|e| e == min_rating).unwrap();
|
||||||
let average = ratings.iter().sum::<u32>() as f32 / ratings.len() as f32;
|
let average = ratings.iter().sum::<u32>() as f32 / ratings.len() as f32;
|
||||||
let partial_cipher = bytes_vector[index].to_hex()[..10].to_string();
|
let partial_cipher = bytes_vector[index].to_hex()[..10].to_string();
|
||||||
if index != 132 {
|
if index != expected_index {
|
||||||
panic!("Regression in challenge 8.");
|
panic!("Regression in challenge 8.");
|
||||||
}
|
}
|
||||||
println!(
|
println!(
|
||||||
"[okay] Challenge 8: Cipher {} [{}...] with rating {} (average = {}) is the solution.",
|
"[okay] Challenge 8: Cipher {} [{}...] with rating {} (average = {}) is the solution.",
|
||||||
index, partial_cipher, min_rating, average
|
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
|
// Write a function that encrypts data under an unknown key --- that is, a
|
||||||
// function that generates a random key and encrypts under it.
|
// function that generates a random key and encrypts under it.
|
||||||
let key = Bytes(random_bytes(16));
|
let key = Bytes(random_bytes(16));
|
||||||
|
|
||||||
// Under the hood, have the function append 5-10 bytes (count chosen randomly)
|
// Under the hood, have the function append 5-10 bytes (count chosen randomly)
|
||||||
// before the plaintext and 5-10 bytes after the plaintext.
|
// before the plaintext and 5-10 bytes after the plaintext.
|
||||||
let _padded_data = pad_data(data.to_vec());
|
let padded_data = pad_data(data.to_vec());
|
||||||
let padded_data = Bytes(data.to_vec());
|
|
||||||
// Now, have the function choose to encrypt under ECB 1/2 the time, and under CBC
|
// 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
|
// the other half (just use random IVs each time for CBC). Use rand(2) to decide
|
||||||
// which to use.
|
// which to use.
|
||||||
@@ -69,9 +67,8 @@ pub fn challenge11() {
|
|||||||
let (data, encryption_type) = if zero_or_one == 1 {
|
let (data, encryption_type) = if zero_or_one == 1 {
|
||||||
(ecb::encrypt(&key, &padded_data), EncryptionType::ECB)
|
(ecb::encrypt(&key, &padded_data), EncryptionType::ECB)
|
||||||
} else {
|
} else {
|
||||||
// let iv = Bytes(random_bytes(16));
|
let iv = Bytes(random_bytes(16));
|
||||||
// (cbc::encrypt(&key, &iv, &padded_data), EncryptionType::CBC)
|
(cbc::encrypt(&key, &iv, &padded_data), EncryptionType::CBC)
|
||||||
(Bytes(random_bytes(2876)), EncryptionType::CBC)
|
|
||||||
};
|
};
|
||||||
(data, encryption_type)
|
(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
|
// 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
|
// with a piece of code that, pointed at a block box that might be encrypting ECB
|
||||||
// or CBC, tells you which one is happening.
|
// or CBC, tells you which one is happening.
|
||||||
|
if data.has_duplicated_cycle(16) {
|
||||||
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 {
|
|
||||||
EncryptionType::ECB
|
EncryptionType::ECB
|
||||||
} else {
|
} else {
|
||||||
EncryptionType::CBC
|
EncryptionType::CBC
|
||||||
@@ -103,6 +85,11 @@ pub fn challenge11() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_oracle(count: usize) {
|
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 text = Bytes::from_utf8("aaaabbbbccccddddaaaabbbbccccddddaaaabbbbccccdddd");
|
||||||
let mut correct: usize = 0;
|
let mut correct: usize = 0;
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
|
|||||||
Reference in New Issue
Block a user