Solve challenge 19 in Python because my Rust still sucks.

main
Felix Martin 2022-07-19 19:35:38 -04:00
parent e70c6c470c
commit 467c3bdbef
5 changed files with 232 additions and 21 deletions

40
data/19.txt Normal file
View File

@ -0,0 +1,40 @@
SSBoYXZlIG1ldCB0aGVtIGF0IGNsb3NlIG9mIGRheQ==
Q29taW5nIHdpdGggdml2aWQgZmFjZXM=
RnJvbSBjb3VudGVyIG9yIGRlc2sgYW1vbmcgZ3JleQ==
RWlnaHRlZW50aC1jZW50dXJ5IGhvdXNlcy4=
SSBoYXZlIHBhc3NlZCB3aXRoIGEgbm9kIG9mIHRoZSBoZWFk
T3IgcG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==
T3IgaGF2ZSBsaW5nZXJlZCBhd2hpbGUgYW5kIHNhaWQ=
UG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==
QW5kIHRob3VnaHQgYmVmb3JlIEkgaGFkIGRvbmU=
T2YgYSBtb2NraW5nIHRhbGUgb3IgYSBnaWJl
VG8gcGxlYXNlIGEgY29tcGFuaW9u
QXJvdW5kIHRoZSBmaXJlIGF0IHRoZSBjbHViLA==
QmVpbmcgY2VydGFpbiB0aGF0IHRoZXkgYW5kIEk=
QnV0IGxpdmVkIHdoZXJlIG1vdGxleSBpcyB3b3JuOg==
QWxsIGNoYW5nZWQsIGNoYW5nZWQgdXR0ZXJseTo=
QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=
VGhhdCB3b21hbidzIGRheXMgd2VyZSBzcGVudA==
SW4gaWdub3JhbnQgZ29vZCB3aWxsLA==
SGVyIG5pZ2h0cyBpbiBhcmd1bWVudA==
VW50aWwgaGVyIHZvaWNlIGdyZXcgc2hyaWxsLg==
V2hhdCB2b2ljZSBtb3JlIHN3ZWV0IHRoYW4gaGVycw==
V2hlbiB5b3VuZyBhbmQgYmVhdXRpZnVsLA==
U2hlIHJvZGUgdG8gaGFycmllcnM/
VGhpcyBtYW4gaGFkIGtlcHQgYSBzY2hvb2w=
QW5kIHJvZGUgb3VyIHdpbmdlZCBob3JzZS4=
VGhpcyBvdGhlciBoaXMgaGVscGVyIGFuZCBmcmllbmQ=
V2FzIGNvbWluZyBpbnRvIGhpcyBmb3JjZTs=
SGUgbWlnaHQgaGF2ZSB3b24gZmFtZSBpbiB0aGUgZW5kLA==
U28gc2Vuc2l0aXZlIGhpcyBuYXR1cmUgc2VlbWVkLA==
U28gZGFyaW5nIGFuZCBzd2VldCBoaXMgdGhvdWdodC4=
VGhpcyBvdGhlciBtYW4gSSBoYWQgZHJlYW1lZA==
QSBkcnVua2VuLCB2YWluLWdsb3Jpb3VzIGxvdXQu
SGUgaGFkIGRvbmUgbW9zdCBiaXR0ZXIgd3Jvbmc=
VG8gc29tZSB3aG8gYXJlIG5lYXIgbXkgaGVhcnQs
WWV0IEkgbnVtYmVyIGhpbSBpbiB0aGUgc29uZzs=
SGUsIHRvbywgaGFzIHJlc2lnbmVkIGhpcyBwYXJ0
SW4gdGhlIGNhc3VhbCBjb21lZHk7
SGUsIHRvbywgaGFzIGJlZW4gY2hhbmdlZCBpbiBoaXMgdHVybiw=
VHJhbnNmb3JtZWQgdXR0ZXJseTo=
QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=

40
data/19_enc.txt Normal file
View File

@ -0,0 +1,40 @@
P/GjKtnHZo+G2yMpBHauUrOYTL/0AmG777V5s8uPCg==
Nb6mIsHFZpWK22t9Gnq1G7bMCr37CGE=
MKOkJo/BKZeN22YvTHyxUraJH7e4DH+xob0/9N2LCg==
M7isI9vHI4yXxy4+CX23B6CVTLT3GGG7vPQ=
P/GjKtnHZpKC3HA4CDO0G6aETL24A32677V5s9uGFjhFxe+v
OaPrO8DOL5aGj244DX2qHLWACa/rTWWxvb5svw==
OaPrI87UI8KPxm06CWGmFvKNG7TxAXf+rrR7s9yPGnw=
Jr6nItvHZo+Gzm00AnSvF6GfTKv3H3at4w==
N7+va9vKKZeEx3d9DnalHaCJTJW4BXO6775w/co=
ObfrKo/PKYGIxm06TGeiHrfMA664DDK5prh6
Ir7rO8PHJ5GGj2J9D3yuArOCBbP2
N6OkPsHGZpaLyiM7BWGmUrOYTKjwCDK9o699vw==
NLSiJciCJYeR22I0AjO3GrOYTKjwCGv+rrR7s+Y=
NKS/a8PLMIeHj3Q1CWGmUr+DGLD9FDK3vPpo/N2ASQ==
N72na8zKJ4yEymdxTHCrE7yLCbi4GGaqqqhz6pU=
N/G/Lt3QL4CPyiM/CXK2BqvMBa+4D32sofQ=
IrmqP4/VKY+CwSQuTHeiC6HMG7nqCDKtv79x5w==
P7/rIsjMKZCCwXd9C3ysFvKbBbD0QQ==
PrS5a8HLIYqX3CM0AjOiALWZAbn2GQ==
I7+/IsOCLoeRj3UyBXCmUrWeCau4HnqsprZzvQ==
IbmqP4/UKYuAyiMwA2GmUqGbCbnsTWa2rrQ/+8qcAA==
IbmuJY/bKZeNyCM8AnfjELeNGajxC2ey4w==
Jbmua93NIofD22x9BHKxALuJHq+n
IrmiOI/PJ4zDx2I5THimAqbMDfzrDnqxoLY=
N7+va93NIofDwHYvTGSqHLWJCPzwAmCtqvQ=
IrmiOI/NMoqG3SM1BWDjGreAHLnqTXOwq/p54caLHXw=
IbC4a8zNK4uNyCM0AmesUrqFH/z+AmC9quE=
PrTrJsbFLpbDx2IrCTO0HbzMCr31CDK3ofpr+8rOFnZJjA==
Jb7rOMrMNYuXxnU4THuqAfKCDajtH3f+vL96/sqKXw==
Jb7rL87QL4yEj2IzCDOwBbeJGPzwBGH+u7Jw5siGBzY=
IrmiOI/NMoqG3SMwDX3jO/KEDbi4CWC7rrd69w==
N/GvOdrMLYeNgyMrDXqtX7WAA67xAmet77Zw5tvA
PrTrI87GZoaMwWZ9AXywBvKOBajsCGD+uKhw/cg=
Ir7rOMDPI8KUx2x9DWGmUryJDa64AGv+p79+4dvC
L7S/a+aCKJeOzWYvTHuqH/KFAvzsBXf+vLVx9JQ=
PrTna9vNKc7Dx2IuTGGmAbuLArn8TXq3vPpv8t2a
P7/rP8fHZoGC3HY8ADOgHb+JCKWj
PrTna9vNKc7Dx2IuTHGmF7zMD7T5A3W7q/p2/Y+GGmsN1Pu5f1c=
IqOqJdzEKZCOymd9GWe3F6CAFeY=
N/G/Lt3QL4CPyiM/CXK2BqvMBa+4D32sofQ=

View File

@ -9,23 +9,29 @@ mod set2;
mod set3;
fn main() {
set1::challenge1();
set1::challenge2();
set1::challenge3();
set1::challenge4();
set1::challenge5();
set1::challenge6();
set1::challenge7();
set1::challenge8();
set2::challenge9();
set2::challenge10();
set2::challenge11();
set2::challenge12();
set2::challenge13();
set2::challenge14();
set2::challenge15();
set2::challenge16();
set3::challenge17();
set3::challenge18();
set3::challenge19();
const RUN_ALL: bool = true;
if RUN_ALL {
set1::challenge1();
set1::challenge2();
set1::challenge3();
set1::challenge4();
set1::challenge5();
set1::challenge6();
set1::challenge7();
set1::challenge8();
set2::challenge9();
set2::challenge10();
set2::challenge11();
set2::challenge12();
set2::challenge13();
set2::challenge14();
set2::challenge15();
set2::challenge16();
set3::challenge17();
set3::challenge18();
set3::challenge19();
set3::challenge20();
} else {
set3::challenge20();
}
}

View File

@ -3,6 +3,7 @@ use crate::bytes_base64::BytesBase64;
use crate::cbc;
use crate::ctr;
use rand::Rng;
use std::fs;
use std::io::{BufRead, BufReader};
pub fn challenge17() {
@ -100,7 +101,7 @@ pub fn challenge18() {
let cleartext = Bytes::from_utf8("Let's see if we can get the party started hard my friends.");
let cipher = ctr::encrypt(&key, 42351234, &cleartext);
let roundtrip = ctr::encrypt(&key, 42351234, &cipher);
let roundtrip = ctr::decrypt(&key, 42351234, &cipher);
assert_eq!(cleartext, roundtrip);
let cipher = BytesBase64::from_base64(
@ -112,5 +113,30 @@ pub fn challenge18() {
}
pub fn challenge19() {
println!("[xxxx] Challenge 19: TBD");
fn read(path: &str) -> Vec<Bytes> {
let file = std::fs::File::open(path).unwrap();
let br = BufReader::new(file);
br.lines()
.map(|line| BytesBase64::from_base64(&line.unwrap()).to_bytes())
.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<Bytes> = 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");
}
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.");
}
pub fn challenge20() {
println!("[xxxx] Challenge 20: TBD");
}

99
src/set3c19.py Normal file
View File

@ -0,0 +1,99 @@
import binascii
import string
from typing import List, Dict, Set
def load_ciphers() -> List[bytes]:
with open("data/19_enc.txt", "r") as f:
r = [binascii.a2b_base64(line) for line in f]
return r
def xor_lookup_set(letters: str) -> Dict[int, Set[str]]:
d = {i: set() for i in range(256)}
for c1 in letters:
for c2 in letters:
xored = ord(c1) ^ ord(c2)
d[xored].add(c1)
d[xored].add(c2)
return d
def attack(ciphers: List[bytes]) -> List[List[str]]:
""" Find out possible characters for each cipher pair and hope that there
is only one possible character. If no character was found add '?' and
if more than one was found add '^'. """
ciphers_len = len(ciphers)
deciphered = [[] for _ in range(ciphers_len)]
max_cipher_len = max(map(len, ciphers))
for byte_index in range(0, max_cipher_len):
# Certain bytes only work with certain letters (found empirically).
if byte_index == 10:
LETTERS = string.ascii_letters + " _-.,;:'"
elif byte_index == 20:
LETTERS = string.ascii_letters + " _-.,;:?"
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
for cipher in ciphers]
possible_chars = [set(LETTERS) for _ in range(ciphers_len)]
for i in range(ciphers_len):
for j in range(i, ciphers_len):
if target_bytes[i] is None or target_bytes[j] is None:
continue
xored = target_bytes[i] ^ target_bytes[j]
chars = lookup[xored]
possible_chars[i] &= chars
possible_chars[j] &= chars
for cipher_index in range(ciphers_len):
if len(ciphers[cipher_index]) <= byte_index:
continue
chars = list(possible_chars[cipher_index])
match len(chars):
case 0:
# print(f"No chars for {cipher_index=} {byte_index=}")
deciphered[cipher_index].append('?')
case 1:
deciphered[cipher_index].append(chars[0].lower())
case 2:
if chars[0].lower() == chars[1].lower():
deciphered[cipher_index].append(chars[0].lower())
else:
# print(f"Two {chars=} {cipher_index=} {byte_index=}")
deciphered[cipher_index].append('^')
case _:
# print(f"Too many {chars=} {cipher_index=} {byte_index=}")
deciphered[cipher_index].append('^')
return deciphered
def manual(decrypts: List[List[str]]) -> List[bytes]:
""" Manually add guessed letters. """
decrypts[0][30] = 'y'
decrypts[2][30] = 'y'
decrypts[4][30] = 'e'
decrypts[4][32] = 'h'
decrypts[4][33] = 'e'
decrypts[4][34] = 'a'
decrypts[4][35] = 'd'
decrypts[6][30] = 'i'
decrypts[13][30] = ' '
decrypts[20][30] = ' '
decrypts[25][30] = 'n'
decrypts[28][30] = ' '
decrypts[29][30] = 't'
decrypts[37][30] = 'i'
decrypts = list(map(lambda l: "".join(l), decrypts))
return decrypts
if __name__ == "__main__":
ciphers = load_ciphers()
decrypts = manual(attack(ciphers))
for d in decrypts:
print(d)