Finish challenge 29 - I didn't find it trivial tbh
This commit is contained in:
98
data/set3c19.py
Normal file
98
data/set3c19.py
Normal 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)
|
||||
Reference in New Issue
Block a user