Finish challenge 29 - I didn't find it trivial tbh

This commit is contained in:
2022-08-19 18:39:29 -04:00
parent 65fe5a7f96
commit c12b1c45a6
4 changed files with 139 additions and 71 deletions

98
data/set3c19.py Normal file
View File

@@ -0,0 +1,98 @@
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)
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)