summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2015-03-28 03:37:58 -0400
committerJesse Luehrs <doy@tozt.net>2015-03-28 03:37:58 -0400
commit301a4e5cdf5d206edc63f2bb632d1e0f0d4cc7f6 (patch)
tree85714f9d3ce0d1d4d7aeb2381f96aab5ae6b2ad2
parent1505a949589b17837319a292a07e1c15ed9296e4 (diff)
downloadmatasano-301a4e5cdf5d206edc63f2bb632d1e0f0d4cc7f6.tar.gz
matasano-301a4e5cdf5d206edc63f2bb632d1e0f0d4cc7f6.zip
reorganize
-rw-r--r--src/aes.rs289
-rw-r--r--src/crack.rs431
-rw-r--r--src/lib.rs24
-rw-r--r--src/xor.rs144
4 files changed, 443 insertions, 445 deletions
diff --git a/src/aes.rs b/src/aes.rs
index 0d262cb..77cbf7a 100644
--- a/src/aes.rs
+++ b/src/aes.rs
@@ -1,17 +1,7 @@
-use std;
-use std::borrow::ToOwned;
-use std::collections::{HashMap, HashSet};
-
use openssl;
use primitives::{fixed_xor, pad_pkcs7, unpad_pkcs7};
-#[derive(PartialEq,Eq,Debug)]
-pub enum BlockCipherMode {
- ECB,
- CBC,
-}
-
pub fn decrypt_aes_128_ecb (bytes: &[u8], key: &[u8]) -> Option<Vec<u8>> {
// openssl already doesn't return differentiable results for invalid
// padding, so we can't either
@@ -63,285 +53,6 @@ pub fn encrypt_aes_128_cbc (bytes: &[u8], key: &[u8], iv: &[u8]) -> Vec<u8> {
return ciphertext;
}
-pub fn find_aes_128_ecb_encrypted_string (inputs: &[Vec<u8>]) -> Vec<u8> {
- let mut max_dups = 0;
- let mut found = vec![];
- for input in inputs {
- let dups = count_duplicate_blocks(input, 16);
- if dups > max_dups {
- max_dups = dups;
- found = input.clone();
- }
- }
- return found;
-}
-
-pub fn detect_ecb_cbc<F> (f: &F, block_size: usize) -> BlockCipherMode where F: Fn(&[u8]) -> Vec<u8> {
- if block_size >= std::u8::MAX as usize {
- panic!("invalid block size: {}", block_size);
- }
- let block_size_byte = block_size as u8;
- let plaintext: Vec<u8> = (0..block_size_byte)
- .cycle()
- .take(block_size * 2)
- .flat_map(|n| std::iter::repeat(n).take(block_size + 1))
- .collect();
- let ciphertext = f(&plaintext[..]);
-
- if count_duplicate_blocks(&ciphertext[..], block_size) >= block_size {
- return BlockCipherMode::ECB;
- }
- else {
- return BlockCipherMode::CBC;
- }
-}
-
-pub fn crack_padded_aes_128_ecb<F> (f: &F) -> Vec<u8> where F: Fn(&[u8]) -> Vec<u8> {
- let block_size = find_block_size(f);
- if detect_ecb_cbc(f, block_size) != BlockCipherMode::ECB {
- panic!("Can only crack ECB-encrypted data");
- }
-
- let mut plaintext = vec![];
-
- let get_block = |input: &[u8], i| {
- let encrypted = f(input);
- let block_number = i / block_size;
- let low = block_number * block_size;
- let high = (block_number + 1) * block_size;
- encrypted[low..high].to_vec()
- };
-
- let mut i = 0;
- loop {
- let mut map = HashMap::new();
-
- let prefix: Vec<u8> = std::iter::repeat(b'A')
- .take(block_size - ((i % block_size) + 1))
- .collect();
- for c in 0..256 {
- let mut prefix_with_next_char = prefix.clone();
- for &c in plaintext.iter() {
- prefix_with_next_char.push(c);
- }
- prefix_with_next_char.push(c as u8);
- map.insert(get_block(&prefix_with_next_char[..], i), c as u8);
- }
-
- let next_char = map.get(&get_block(&prefix[..], i));
- if next_char.is_some() {
- plaintext.push(*next_char.unwrap());
- }
- else {
- break;
- }
-
- i += 1;
- }
-
- return unpad_pkcs7(&plaintext[..]).expect("invalid padding").to_vec();
-}
-
-pub fn crack_padded_aes_128_ecb_with_prefix<F> (f: &F) -> Vec<u8> where F: Fn(&[u8]) -> Vec<u8> {
- let (block_size, prefix_len) = find_block_size_and_fixed_prefix_len(f);
- let wrapped_f = |input: &[u8]| {
- let alignment_padding = block_size - (prefix_len % block_size);
- let padded_input: Vec<u8> = std::iter::repeat(b'A')
- .take(alignment_padding)
- .chain(input.iter().map(|x| *x))
- .collect();
- return f(&padded_input[..])
- .iter()
- .skip(prefix_len + alignment_padding)
- .map(|x| *x)
- .collect();
- };
- return crack_padded_aes_128_ecb(&wrapped_f);
-}
-
-pub fn crack_querystring_aes_128_ecb<F> (encrypter: &F) -> (String, Vec<Vec<u8>>) where F: Fn(&str) -> Vec<u8> {
- fn incr_map_element (map: &mut HashMap<Vec<u8>, usize>, key: Vec<u8>) {
- if let Some(val) = map.get_mut(&key) {
- *val += 1;
- return;
- }
- map.insert(key, 1);
- };
-
- // find blocks that correspond to "uid=10&role=user" or "role=user&uid=10"
- let find_uid_role_blocks = || {
- let mut map = HashMap::new();
- for c in 32..127 {
- let email_bytes: Vec<u8> = std::iter::repeat(c).take(9).collect();
- let email = std::str::from_utf8(&email_bytes[..]).unwrap();
- let ciphertext = encrypter(email);
- incr_map_element(&mut map, ciphertext[..16].to_vec());
- incr_map_element(&mut map, ciphertext[16..32].to_vec());
- }
-
- let mut most_common_blocks = vec![];
- for (k, v) in map {
- most_common_blocks.push((k, v));
- if most_common_blocks.len() > 2 {
- let (idx, _) = most_common_blocks
- .iter()
- .enumerate()
- .fold(
- (0, (vec![], 10000)),
- |(aidx, (ablock, acount)), (idx, &(ref block, count))| {
- if count < acount {
- (idx, (block.clone(), count))
- }
- else {
- (aidx, (ablock.clone(), acount))
- }
- }
- );
- most_common_blocks.swap_remove(idx);
- }
- }
-
- if let [(ref block1, _), (ref block2, _)] = &most_common_blocks[..] {
- return (block1.clone(), block2.clone());
- }
- else {
- panic!("couldn't find most common blocks");
- }
- };
-
- // encrypt:
- // email=..........admin<pcks7 padding>...............&uid=10&role=user
- let calculate_admin_block = |block1, block2| {
- for _ in 0..1000 {
- let email = "blorg@bar.admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b...............";
- let ciphertext = encrypter(email);
- if &ciphertext[48..64] == block1 || &ciphertext[48..64] == block2 {
- return ciphertext[16..32].to_vec();
- }
- }
- panic!("couldn't find a ciphertext with the correct role/uid block");
- };
-
- // find all possible encryptions with email=............ and then replace
- // the last block with the padded admin block above
- let calculate_possible_admin_ciphertexts = |admin_block: Vec<u8>| {
- let email = "blorg@bar.com";
- let mut possibles = vec![];
- while possibles.len() < 6 {
- let ciphertext = encrypter(email);
- let modified_ciphertext = ciphertext
- .iter()
- .take(32)
- .chain(admin_block.iter())
- .map(|x| *x)
- .collect();
- if !possibles.iter().any(|possible| possible == &modified_ciphertext) {
- possibles.push(modified_ciphertext);
- }
- }
- return (email.to_owned(), possibles);
- };
-
- let (block1, block2) = find_uid_role_blocks();
- let admin_block = calculate_admin_block(block1, block2);
- return calculate_possible_admin_ciphertexts(admin_block);
-}
-
-pub fn crack_cbc_bitflipping<F> (f: &F) -> Vec<u8> where F: Fn(&str) -> Vec<u8> {
- let mut ciphertext = f("AAAAAAAAAAAAAAAA:admin<true:AAAA");
- ciphertext[32] = ciphertext[32] ^ 0x01;
- ciphertext[38] = ciphertext[38] ^ 0x01;
- ciphertext[43] = ciphertext[43] ^ 0x01;
- return ciphertext;
-}
-
-pub fn crack_cbc_padding_oracle<F> (iv: &[u8], ciphertext: &[u8], f: &F) -> Vec<u8> where F: Fn(&[u8], &[u8]) -> bool {
- let mut prev = iv;
- let mut plaintext = vec![];
- for block in ciphertext.chunks(16) {
- let mut plaintext_block = vec![];
- 'BYTE: for byte in 0..16u8 {
- for c_int in 0..256 {
- let c = (255 - c_int) as u8;
- let offset = (16 - byte - 1) as usize;
- let mut iv: Vec<u8> = prev
- .iter()
- .take(offset)
- .map(|x| *x)
- .collect();
- iv.push(prev[offset] ^ c ^ (byte + 1));
- for i in 0..(byte as usize) {
- iv.push(prev[offset + i + 1] ^ plaintext_block[i] ^ (byte + 1));
- }
- if f(&iv[..], block) {
- plaintext_block.insert(0, c);
- continue 'BYTE;
- }
- }
- panic!("no byte found! ({})", byte);
- }
- for c in plaintext_block {
- plaintext.push(c);
- }
- prev = block;
- }
- return unpad_pkcs7(&plaintext[..]).unwrap().to_vec();
-}
-
-fn count_duplicate_blocks (input: &[u8], block_size: usize) -> usize {
- let mut set = HashSet::new();
- let mut dups = 0;
- for block in input.chunks(block_size) {
- if !set.insert(block) {
- dups += 1;
- }
- }
- return dups;
-}
-
-fn find_block_size<F> (f: &F) -> usize where F: Fn(&[u8]) -> Vec<u8> {
- let (block_size, _) = find_block_size_and_fixed_prefix_len(f);
- return block_size;
-}
-
-fn find_block_size_and_fixed_prefix_len<F> (f: &F) -> (usize, usize) where F: Fn(&[u8]) -> Vec<u8> {
- let fixed_prefix_len = find_fixed_block_prefix_len(f);
- let byte = b'A';
- let mut prev = f(&[b'f']);
- let mut len = 0;
- loop {
- let prefix: Vec<u8> = std::iter::repeat(byte)
- .take(len)
- .collect();
- let next = f(&prefix[..]);
-
- let prefix_len = shared_prefix_len(
- prev.iter(),
- next.iter()
- );
- if prefix_len > fixed_prefix_len {
- let block_size = prefix_len - fixed_prefix_len;
- return (block_size, fixed_prefix_len + block_size - (len - 1));
- }
-
- prev = next;
- len += 1;
- }
-}
-
-fn find_fixed_block_prefix_len<F> (f: &F) -> usize where F: Fn(&[u8]) -> Vec<u8> {
- let ciphertext1 = f(b"");
- let ciphertext2 = f(b"A");
- return shared_prefix_len(ciphertext1.iter(), ciphertext2.iter());
-}
-
-fn shared_prefix_len<I> (i1: I, i2: I) -> usize where I: Iterator, <I as Iterator>::Item: PartialEq {
- return i1
- .zip(i2)
- .take_while(|&(ref c1, ref c2)| { c1 == c2 })
- .count();
-}
-
#[test]
fn test_encrypt_decrypt () {
let plaintext = b"Summertime and the wind is blowing outside in lower \
diff --git a/src/crack.rs b/src/crack.rs
new file mode 100644
index 0000000..979c0c2
--- /dev/null
+++ b/src/crack.rs
@@ -0,0 +1,431 @@
+use std;
+use std::ascii::AsciiExt;
+use std::borrow::ToOwned;
+use std::collections::{HashMap, HashSet};
+use std::num::Float;
+
+use data::ENGLISH_FREQUENCIES;
+use primitives::{fixed_xor, unpad_pkcs7, hamming, repeating_key_xor};
+
+#[derive(PartialEq,Eq,Debug)]
+pub enum BlockCipherMode {
+ ECB,
+ CBC,
+}
+
+pub fn find_single_byte_xor_encrypted_string (inputs: &[Vec<u8>]) -> Vec<u8> {
+ let mut min_diff = 100.0;
+ let mut best_decrypted = vec![];
+ for input in inputs {
+ let (key, diff) = crack_single_byte_xor_with_confidence(input);
+ if diff < min_diff {
+ min_diff = diff;
+ best_decrypted = repeating_key_xor(input, &[key]);
+ }
+ }
+ return best_decrypted;
+}
+
+pub fn crack_single_byte_xor (input: &[u8]) -> Vec<u8> {
+ let (key, _) = crack_single_byte_xor_with_confidence(input);
+ return repeating_key_xor(input, &[key]);
+}
+
+pub fn crack_repeating_key_xor (input: &[u8]) -> Vec<u8> {
+ let mut keysizes = vec![];
+ for keysize in 2..40 {
+ let distance1 = hamming(
+ &input[(keysize * 0)..(keysize * 1)],
+ &input[(keysize * 1)..(keysize * 2)]
+ ) as f64;
+ let distance2 = hamming(
+ &input[(keysize * 1)..(keysize * 2)],
+ &input[(keysize * 2)..(keysize * 3)]
+ ) as f64;
+ let distance3 = hamming(
+ &input[(keysize * 2)..(keysize * 3)],
+ &input[(keysize * 3)..(keysize * 4)]
+ ) as f64;
+ let distance = distance1 + distance2 + distance3 / 3.0;
+ let normal_distance = distance / (keysize as f64);
+ keysizes.push((keysize, normal_distance));
+ if keysizes.len() > 5 {
+ let (idx, _) = keysizes
+ .iter()
+ .enumerate()
+ .fold(
+ (0, (0, 0.0)),
+ |(accidx, (accsize, accdist)), (idx, &(size, dist))| {
+ if dist > accdist {
+ (idx, (size, dist))
+ }
+ else {
+ (accidx, (accsize, accdist))
+ }
+ }
+ );
+ keysizes.swap_remove(idx);
+ }
+ }
+
+ let mut min_diff = 100.0;
+ let mut best_key = vec![];
+ for (keysize, _) in keysizes {
+ let strides: Vec<Vec<u8>> = (0..keysize)
+ .map(|n| {
+ // XXX sigh ):
+ let mut elts = vec![];
+ for (i, &c) in input.iter().enumerate() {
+ if i % keysize == n {
+ elts.push(c);
+ }
+ }
+ elts
+ })
+ .collect();
+ let cracked: Vec<(u8, f64)> = strides
+ .iter()
+ .map(|input| crack_single_byte_xor_with_confidence(input))
+ .collect();
+ let diff = cracked
+ .iter()
+ .map(|&(_, diff)| diff)
+ .fold(0.0, |acc, x| acc + x);
+ let key = cracked
+ .iter()
+ .map(|&(c, _)| c)
+ .collect();
+ let normal_diff = diff / (keysize as f64);
+ if normal_diff < min_diff {
+ min_diff = normal_diff;
+ best_key = key;
+ }
+ }
+
+ return repeating_key_xor(input, &best_key[..]);
+}
+
+pub fn find_aes_128_ecb_encrypted_string (inputs: &[Vec<u8>]) -> Vec<u8> {
+ let mut max_dups = 0;
+ let mut found = vec![];
+ for input in inputs {
+ let dups = count_duplicate_blocks(input, 16);
+ if dups > max_dups {
+ max_dups = dups;
+ found = input.clone();
+ }
+ }
+ return found;
+}
+
+pub fn detect_ecb_cbc<F> (f: &F, block_size: usize) -> BlockCipherMode where F: Fn(&[u8]) -> Vec<u8> {
+ if block_size >= std::u8::MAX as usize {
+ panic!("invalid block size: {}", block_size);
+ }
+ let block_size_byte = block_size as u8;
+ let plaintext: Vec<u8> = (0..block_size_byte)
+ .cycle()
+ .take(block_size * 2)
+ .flat_map(|n| std::iter::repeat(n).take(block_size + 1))
+ .collect();
+ let ciphertext = f(&plaintext[..]);
+
+ if count_duplicate_blocks(&ciphertext[..], block_size) >= block_size {
+ return BlockCipherMode::ECB;
+ }
+ else {
+ return BlockCipherMode::CBC;
+ }
+}
+
+pub fn crack_padded_aes_128_ecb<F> (f: &F) -> Vec<u8> where F: Fn(&[u8]) -> Vec<u8> {
+ let block_size = find_block_size(f);
+ if detect_ecb_cbc(f, block_size) != BlockCipherMode::ECB {
+ panic!("Can only crack ECB-encrypted data");
+ }
+
+ let mut plaintext = vec![];
+
+ let get_block = |input: &[u8], i| {
+ let encrypted = f(input);
+ let block_number = i / block_size;
+ let low = block_number * block_size;
+ let high = (block_number + 1) * block_size;
+ encrypted[low..high].to_vec()
+ };
+
+ let mut i = 0;
+ loop {
+ let mut map = HashMap::new();
+
+ let prefix: Vec<u8> = std::iter::repeat(b'A')
+ .take(block_size - ((i % block_size) + 1))
+ .collect();
+ for c in 0..256 {
+ let mut prefix_with_next_char = prefix.clone();
+ for &c in plaintext.iter() {
+ prefix_with_next_char.push(c);
+ }
+ prefix_with_next_char.push(c as u8);
+ map.insert(get_block(&prefix_with_next_char[..], i), c as u8);
+ }
+
+ let next_char = map.get(&get_block(&prefix[..], i));
+ if next_char.is_some() {
+ plaintext.push(*next_char.unwrap());
+ }
+ else {
+ break;
+ }
+
+ i += 1;
+ }
+
+ return unpad_pkcs7(&plaintext[..]).expect("invalid padding").to_vec();
+}
+
+pub fn crack_padded_aes_128_ecb_with_prefix<F> (f: &F) -> Vec<u8> where F: Fn(&[u8]) -> Vec<u8> {
+ let (block_size, prefix_len) = find_block_size_and_fixed_prefix_len(f);
+ let wrapped_f = |input: &[u8]| {
+ let alignment_padding = block_size - (prefix_len % block_size);
+ let padded_input: Vec<u8> = std::iter::repeat(b'A')
+ .take(alignment_padding)
+ .chain(input.iter().map(|x| *x))
+ .collect();
+ return f(&padded_input[..])
+ .iter()
+ .skip(prefix_len + alignment_padding)
+ .map(|x| *x)
+ .collect();
+ };
+ return crack_padded_aes_128_ecb(&wrapped_f);
+}
+
+pub fn crack_querystring_aes_128_ecb<F> (encrypter: &F) -> (String, Vec<Vec<u8>>) where F: Fn(&str) -> Vec<u8> {
+ fn incr_map_element (map: &mut HashMap<Vec<u8>, usize>, key: Vec<u8>) {
+ if let Some(val) = map.get_mut(&key) {
+ *val += 1;
+ return;
+ }
+ map.insert(key, 1);
+ };
+
+ // find blocks that correspond to "uid=10&role=user" or "role=user&uid=10"
+ let find_uid_role_blocks = || {
+ let mut map = HashMap::new();
+ for c in 32..127 {
+ let email_bytes: Vec<u8> = std::iter::repeat(c).take(9).collect();
+ let email = std::str::from_utf8(&email_bytes[..]).unwrap();
+ let ciphertext = encrypter(email);
+ incr_map_element(&mut map, ciphertext[..16].to_vec());
+ incr_map_element(&mut map, ciphertext[16..32].to_vec());
+ }
+
+ let mut most_common_blocks = vec![];
+ for (k, v) in map {
+ most_common_blocks.push((k, v));
+ if most_common_blocks.len() > 2 {
+ let (idx, _) = most_common_blocks
+ .iter()
+ .enumerate()
+ .fold(
+ (0, (vec![], 10000)),
+ |(aidx, (ablock, acount)), (idx, &(ref block, count))| {
+ if count < acount {
+ (idx, (block.clone(), count))
+ }
+ else {
+ (aidx, (ablock.clone(), acount))
+ }
+ }
+ );
+ most_common_blocks.swap_remove(idx);
+ }
+ }
+
+ if let [(ref block1, _), (ref block2, _)] = &most_common_blocks[..] {
+ return (block1.clone(), block2.clone());
+ }
+ else {
+ panic!("couldn't find most common blocks");
+ }
+ };
+
+ // encrypt:
+ // email=..........admin<pcks7 padding>...............&uid=10&role=user
+ let calculate_admin_block = |block1, block2| {
+ for _ in 0..1000 {
+ let email = "blorg@bar.admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b...............";
+ let ciphertext = encrypter(email);
+ if &ciphertext[48..64] == block1 || &ciphertext[48..64] == block2 {
+ return ciphertext[16..32].to_vec();
+ }
+ }
+ panic!("couldn't find a ciphertext with the correct role/uid block");
+ };
+
+ // find all possible encryptions with email=............ and then replace
+ // the last block with the padded admin block above
+ let calculate_possible_admin_ciphertexts = |admin_block: Vec<u8>| {
+ let email = "blorg@bar.com";
+ let mut possibles = vec![];
+ while possibles.len() < 6 {
+ let ciphertext = encrypter(email);
+ let modified_ciphertext = ciphertext
+ .iter()
+ .take(32)
+ .chain(admin_block.iter())
+ .map(|x| *x)
+ .collect();
+ if !possibles.iter().any(|possible| possible == &modified_ciphertext) {
+ possibles.push(modified_ciphertext);
+ }
+ }
+ return (email.to_owned(), possibles);
+ };
+
+ let (block1, block2) = find_uid_role_blocks();
+ let admin_block = calculate_admin_block(block1, block2);
+ return calculate_possible_admin_ciphertexts(admin_block);
+}
+
+pub fn crack_cbc_bitflipping<F> (f: &F) -> Vec<u8> where F: Fn(&str) -> Vec<u8> {
+ let mut ciphertext = f("AAAAAAAAAAAAAAAA:admin<true:AAAA");
+ ciphertext[32] = ciphertext[32] ^ 0x01;
+ ciphertext[38] = ciphertext[38] ^ 0x01;
+ ciphertext[43] = ciphertext[43] ^ 0x01;
+ return ciphertext;
+}
+
+pub fn crack_cbc_padding_oracle<F> (iv: &[u8], ciphertext: &[u8], f: &F) -> Vec<u8> where F: Fn(&[u8], &[u8]) -> bool {
+ let mut prev = iv;
+ let mut plaintext = vec![];
+ for block in ciphertext.chunks(16) {
+ let mut plaintext_block = vec![];
+ 'BYTE: for byte in 0..16u8 {
+ for c_int in 0..256 {
+ let c = (255 - c_int) as u8;
+ let offset = (16 - byte - 1) as usize;
+ let mut iv: Vec<u8> = prev
+ .iter()
+ .take(offset)
+ .map(|x| *x)
+ .collect();
+ iv.push(prev[offset] ^ c ^ (byte + 1));
+ for i in 0..(byte as usize) {
+ iv.push(prev[offset + i + 1] ^ plaintext_block[i] ^ (byte + 1));
+ }
+ if f(&iv[..], block) {
+ plaintext_block.insert(0, c);
+ continue 'BYTE;
+ }
+ }
+ panic!("no byte found! ({})", byte);
+ }
+ for c in plaintext_block {
+ plaintext.push(c);
+ }
+ prev = block;
+ }
+ return unpad_pkcs7(&plaintext[..]).unwrap().to_vec();
+}
+
+fn crack_single_byte_xor_with_confidence (input: &[u8]) -> (u8, f64) {
+ let mut min_diff = 100.0;
+ let mut best_key = 0;
+ for a in 0..256u16 {
+ let decrypted = fixed_xor(
+ input,
+ &std::iter::repeat(a as u8)
+ .take(input.len())
+ .collect::<Vec<u8>>()[..]
+ );
+ if !decrypted.is_ascii() {
+ continue;
+ }
+ if decrypted.iter().any(|&c| c != b'\n' && (c < 0x20 || c > 0x7E)) {
+ continue;
+ }
+ let lowercase = decrypted.to_ascii_lowercase();
+ let mut frequencies = [0; 26];
+ let mut total_frequency = 0;
+ let mut extra_frequencies = 0;
+ for c in lowercase {
+ total_frequency += 1;
+ if c >= 0x61 && c <= 0x7A {
+ frequencies[(c - 0x61) as usize] += 1;
+ }
+ else {
+ extra_frequencies += 1;
+ }
+ }
+
+ let mut total_diff = 0.0;
+ for (&english, &crypt) in ENGLISH_FREQUENCIES.iter().zip(frequencies.iter()) {
+ let relative_frequency = (crypt as f64) / (total_frequency as f64);
+ total_diff += (english - relative_frequency).abs();
+ }
+ total_diff += (extra_frequencies as f64) / (total_frequency as f64);
+
+ if total_diff < min_diff {
+ min_diff = total_diff;
+ best_key = a as u8;
+ }
+ }
+
+ return (best_key, min_diff);
+}
+
+fn count_duplicate_blocks (input: &[u8], block_size: usize) -> usize {
+ let mut set = HashSet::new();
+ let mut dups = 0;
+ for block in input.chunks(block_size) {
+ if !set.insert(block) {
+ dups += 1;
+ }
+ }
+ return dups;
+}
+
+fn find_block_size<F> (f: &F) -> usize where F: Fn(&[u8]) -> Vec<u8> {
+ let (block_size, _) = find_block_size_and_fixed_prefix_len(f);
+ return block_size;
+}
+
+fn find_block_size_and_fixed_prefix_len<F> (f: &F) -> (usize, usize) where F: Fn(&[u8]) -> Vec<u8> {
+ let fixed_prefix_len = find_fixed_block_prefix_len(f);
+ let byte = b'A';
+ let mut prev = f(&[b'f']);
+ let mut len = 0;
+ loop {
+ let prefix: Vec<u8> = std::iter::repeat(byte)
+ .take(len)
+ .collect();
+ let next = f(&prefix[..]);
+
+ let prefix_len = shared_prefix_len(
+ prev.iter(),
+ next.iter()
+ );
+ if prefix_len > fixed_prefix_len {
+ let block_size = prefix_len - fixed_prefix_len;
+ return (block_size, fixed_prefix_len + block_size - (len - 1));
+ }
+
+ prev = next;
+ len += 1;
+ }
+}
+
+fn find_fixed_block_prefix_len<F> (f: &F) -> usize where F: Fn(&[u8]) -> Vec<u8> {
+ let ciphertext1 = f(b"");
+ let ciphertext2 = f(b"A");
+ return shared_prefix_len(ciphertext1.iter(), ciphertext2.iter());
+}
+
+fn shared_prefix_len<I> (i1: I, i2: I) -> usize where I: Iterator, <I as Iterator>::Item: PartialEq {
+ return i1
+ .zip(i2)
+ .take_while(|&(ref c1, ref c2)| { c1 == c2 })
+ .count();
+}
diff --git a/src/lib.rs b/src/lib.rs
index d11f20e..875544b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,23 +3,15 @@ extern crate openssl;
mod aes;
mod base64;
+mod crack;
mod data;
mod http;
mod primitives;
-mod xor;
-pub use aes::BlockCipherMode;
pub use aes::decrypt_aes_128_ecb;
pub use aes::decrypt_aes_128_cbc;
pub use aes::encrypt_aes_128_ecb;
pub use aes::encrypt_aes_128_cbc;
-pub use aes::find_aes_128_ecb_encrypted_string;
-pub use aes::detect_ecb_cbc;
-pub use aes::crack_padded_aes_128_ecb;
-pub use aes::crack_padded_aes_128_ecb_with_prefix;
-pub use aes::crack_querystring_aes_128_ecb;
-pub use aes::crack_cbc_bitflipping;
-pub use aes::crack_cbc_padding_oracle;
pub use base64::to_base64;
pub use http::parse_query_string;
pub use http::create_query_string;
@@ -27,6 +19,14 @@ pub use primitives::fixed_xor;
pub use primitives::pad_pkcs7;
pub use primitives::unpad_pkcs7;
pub use primitives::repeating_key_xor;
-pub use xor::find_single_byte_xor_encrypted_string;
-pub use xor::crack_single_byte_xor;
-pub use xor::crack_repeating_key_xor;
+pub use crack::BlockCipherMode;
+pub use crack::find_aes_128_ecb_encrypted_string;
+pub use crack::detect_ecb_cbc;
+pub use crack::crack_padded_aes_128_ecb;
+pub use crack::crack_padded_aes_128_ecb_with_prefix;
+pub use crack::crack_querystring_aes_128_ecb;
+pub use crack::crack_cbc_bitflipping;
+pub use crack::crack_cbc_padding_oracle;
+pub use crack::find_single_byte_xor_encrypted_string;
+pub use crack::crack_single_byte_xor;
+pub use crack::crack_repeating_key_xor;
diff --git a/src/xor.rs b/src/xor.rs
deleted file mode 100644
index f55a7a2..0000000
--- a/src/xor.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-use data::ENGLISH_FREQUENCIES;
-use primitives::{fixed_xor, hamming, repeating_key_xor};
-
-use std;
-use std::ascii::AsciiExt;
-use std::num::Float;
-
-pub fn find_single_byte_xor_encrypted_string (inputs: &[Vec<u8>]) -> Vec<u8> {
- let mut min_diff = 100.0;
- let mut best_decrypted = vec![];
- for input in inputs {
- let (key, diff) = crack_single_byte_xor_with_confidence(input);
- if diff < min_diff {
- min_diff = diff;
- best_decrypted = repeating_key_xor(input, &[key]);
- }
- }
- return best_decrypted;
-}
-
-pub fn crack_single_byte_xor (input: &[u8]) -> Vec<u8> {
- let (key, _) = crack_single_byte_xor_with_confidence(input);
- return repeating_key_xor(input, &[key]);
-}
-
-pub fn crack_repeating_key_xor (input: &[u8]) -> Vec<u8> {
- let mut keysizes = vec![];
- for keysize in 2..40 {
- let distance1 = hamming(
- &input[(keysize * 0)..(keysize * 1)],
- &input[(keysize * 1)..(keysize * 2)]
- ) as f64;
- let distance2 = hamming(
- &input[(keysize * 1)..(keysize * 2)],
- &input[(keysize * 2)..(keysize * 3)]
- ) as f64;
- let distance3 = hamming(
- &input[(keysize * 2)..(keysize * 3)],
- &input[(keysize * 3)..(keysize * 4)]
- ) as f64;
- let distance = distance1 + distance2 + distance3 / 3.0;
- let normal_distance = distance / (keysize as f64);
- keysizes.push((keysize, normal_distance));
- if keysizes.len() > 5 {
- let (idx, _) = keysizes
- .iter()
- .enumerate()
- .fold(
- (0, (0, 0.0)),
- |(accidx, (accsize, accdist)), (idx, &(size, dist))| {
- if dist > accdist {
- (idx, (size, dist))
- }
- else {
- (accidx, (accsize, accdist))
- }
- }
- );
- keysizes.swap_remove(idx);
- }
- }
-
- let mut min_diff = 100.0;
- let mut best_key = vec![];
- for (keysize, _) in keysizes {
- let strides: Vec<Vec<u8>> = (0..keysize)
- .map(|n| {
- // XXX sigh ):
- let mut elts = vec![];
- for (i, &c) in input.iter().enumerate() {
- if i % keysize == n {
- elts.push(c);
- }
- }
- elts
- })
- .collect();
- let cracked: Vec<(u8, f64)> = strides
- .iter()
- .map(|input| crack_single_byte_xor_with_confidence(input))
- .collect();
- let diff = cracked
- .iter()
- .map(|&(_, diff)| diff)
- .fold(0.0, |acc, x| acc + x);
- let key = cracked
- .iter()
- .map(|&(c, _)| c)
- .collect();
- let normal_diff = diff / (keysize as f64);
- if normal_diff < min_diff {
- min_diff = normal_diff;
- best_key = key;
- }
- }
-
- return repeating_key_xor(input, &best_key[..]);
-}
-
-fn crack_single_byte_xor_with_confidence (input: &[u8]) -> (u8, f64) {
- let mut min_diff = 100.0;
- let mut best_key = 0;
- for a in 0..256u16 {
- let decrypted = fixed_xor(
- input,
- &std::iter::repeat(a as u8)
- .take(input.len())
- .collect::<Vec<u8>>()[..]
- );
- if !decrypted.is_ascii() {
- continue;
- }
- if decrypted.iter().any(|&c| c != b'\n' && (c < 0x20 || c > 0x7E)) {
- continue;
- }
- let lowercase = decrypted.to_ascii_lowercase();
- let mut frequencies = [0; 26];
- let mut total_frequency = 0;
- let mut extra_frequencies = 0;
- for c in lowercase {
- total_frequency += 1;
- if c >= 0x61 && c <= 0x7A {
- frequencies[(c - 0x61) as usize] += 1;
- }
- else {
- extra_frequencies += 1;
- }
- }
-
- let mut total_diff = 0.0;
- for (&english, &crypt) in ENGLISH_FREQUENCIES.iter().zip(frequencies.iter()) {
- let relative_frequency = (crypt as f64) / (total_frequency as f64);
- total_diff += (english - relative_frequency).abs();
- }
- total_diff += (extra_frequencies as f64) / (total_frequency as f64);
-
- if total_diff < min_diff {
- min_diff = total_diff;
- best_key = a as u8;
- }
- }
-
- return (best_key, min_diff);
-}