diff --git a/src/main.rs b/src/main.rs index 2979b3d..5fb88ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ mod set2; mod set3; fn main() { - const RUN_ALL: bool = false; + const RUN_ALL: bool = true; if RUN_ALL { set1::challenge1(); set1::challenge2(); @@ -32,6 +32,6 @@ fn main() { set3::challenge19(); set3::challenge20(); } else { - set3::challenge19(); + set3::challenge20(); } } diff --git a/src/set3.rs b/src/set3.rs index d8260b3..5e9c857 100644 --- a/src/set3.rs +++ b/src/set3.rs @@ -6,7 +6,6 @@ use rand::Rng; use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; -use std::fs; use std::io::{BufRead, BufReader}; pub fn challenge17() { @@ -124,18 +123,6 @@ pub fn challenge19() { .collect() } - let plaintexts = read("data/19.txt"); - let key = Bytes::from_utf8("YELLOW SUBMARINE"); - let encrypt = |plaintext: &Bytes| -> Bytes { ctr::encrypt(&key, 0, plaintext) }; - let ciphers: Vec = plaintexts.iter().map(|ct| encrypt(&ct)).collect(); - let mut data = String::new(); - for cipher in ciphers.iter() { - let copy_cipher = Bytes(cipher.0.to_vec()); - let cipher_base64 = BytesBase64::from_bytes(copy_cipher); - data.push_str(&cipher_base64.to_string()); - data.push_str("\n"); - } - fn xor_to_char_set(letters: &Vec) -> HashMap>> { let mut h = HashMap::new(); for i in 0..255_u8 { @@ -154,17 +141,130 @@ pub fn challenge19() { h } - fn _attack(_ciphers: Vec) -> Vec> { - vec![vec![]] + fn u8_lower(s: u8) -> u8 { + if s >= b'A' && s <= b'Z' { + return s + 32; + } + s } - let mut letters: Vec = (0..255_u8).filter(u8::is_ascii_alphabetic).collect(); - letters.append(&mut vec![b'_', b'-', b'.']); - let lookup = xor_to_char_set(&letters); - println!("{:?}", lookup); + fn ascii_letters(additional: &str) -> Vec { + let mut letters: Vec = (0..255_u8).filter(u8::is_ascii_alphabetic).collect(); + for b in additional.as_bytes() { + letters.push(*b); + } + letters + } - fs::write("data/19_enc.txt", data).expect("Unable to write file"); - println!("[okay] Challenge 19: encrypted to data/19_enc.txt, attack in set3c19.py."); + fn attack(ciphers: Vec) -> Vec>> { + let ciphers_len = ciphers.len(); + let deciphered = vec![RefCell::new(vec![]); ciphers_len]; + let max_cipher_len = ciphers.iter().map(|c| c.len()).max().unwrap_or(0); + + for byte_index in 0..max_cipher_len { + let letters = match byte_index { + // chars that work for 10 and 20 found via trial and error + 10 => ascii_letters(" _-.,;:'"), + 20 => ascii_letters(" _-.,;:?"), + _ => ascii_letters(" _-.,;:"), + }; + let lookup = xor_to_char_set(&letters); + + let target_bytes: Vec> = ciphers + .iter() + .map(|c| { + if c.len() > byte_index { + Some(c.0[byte_index]) + } else { + None + } + }) + .collect(); + let mut possible_chars: Vec> = ciphers + .iter() + .map(|_| HashSet::from_iter(letters.iter().cloned())) + .collect(); + + for i in 0..ciphers_len { + for j in i..ciphers_len { + if target_bytes[i] == None || target_bytes[j] == None { + continue; + } + let xored = target_bytes[i].unwrap() ^ target_bytes[j].unwrap(); + let chars = lookup.get(&xored).unwrap().borrow(); + possible_chars[i] = possible_chars[i].intersection(&chars).cloned().collect(); + possible_chars[j] = possible_chars[j].intersection(&chars).cloned().collect(); + } + } + + for cipher_index in 0..ciphers_len { + if ciphers[cipher_index].len() <= byte_index { + continue; + } + let chars: Vec = possible_chars[cipher_index].iter().cloned().collect(); + match chars.len() { + 0 => { + // println!("No chars for {cipher_index} {byte_index}"); + deciphered[cipher_index].borrow_mut().push(b'?'); + } + 1 => { + deciphered[cipher_index] + .borrow_mut() + .push(u8_lower(chars[0])); + } + 2 => { + if u8_lower(chars[0]) == u8_lower(chars[1]) { + deciphered[cipher_index] + .borrow_mut() + .push(u8_lower(chars[0])); + } else { + // println!("Two {chars:?} {cipher_index} {byte_index}"); + deciphered[cipher_index].borrow_mut().push(b'^'); + } + } + _ => { + // println!("Two {chars:?} {cipher_index} {byte_index}"); + deciphered[cipher_index].borrow_mut().push(b'^'); + } + } + } + } + + deciphered + } + + fn manual(decrypts: &Vec>>) { + // Add manually guessed letters + decrypts[0].borrow_mut()[30] = b'y'; + decrypts[2].borrow_mut()[30] = b'y'; + let mut d4 = decrypts[4].borrow_mut(); + d4[30] = b'e'; + d4[32] = b'h'; + d4[33] = b'e'; + d4[34] = b'a'; + d4[35] = b'd'; + decrypts[6].borrow_mut()[30] = b'i'; + decrypts[13].borrow_mut()[30] = b' '; + decrypts[20].borrow_mut()[30] = b' '; + decrypts[25].borrow_mut()[30] = b'n'; + decrypts[28].borrow_mut()[30] = b' '; + decrypts[29].borrow_mut()[30] = b't'; + decrypts[37].borrow_mut()[30] = b'i'; + } + + let plaintexts = read("data/19.txt"); + let key = Bytes::from_utf8("YELLOW SUBMARINE"); + let encrypt = |plaintext: &Bytes| -> Bytes { ctr::encrypt(&key, 0, plaintext) }; + let ciphers: Vec = plaintexts.iter().map(|ct| encrypt(&ct)).collect(); + let decrypts = attack(ciphers); + manual(&decrypts); + for row in decrypts { + println!( + "[okay] Challenge 19: {}", + Bytes(row.borrow().to_vec()).to_utf8() + ); + break; + } } pub fn challenge20() { diff --git a/src/set3c19.py b/src/set3c19.py index fc44724..afb4a2f 100644 --- a/src/set3c19.py +++ b/src/set3c19.py @@ -36,7 +36,6 @@ def attack(ciphers: List[bytes]) -> List[List[str]]: else: LETTERS = string.ascii_letters + " _-.,;:" lookup = xor_lookup_set(LETTERS) - ciphers_len = len(ciphers) target_bytes = [cipher[byte_index] if len(cipher) > byte_index else None