diff --git a/cli.py b/cli.py index 68adcef..bd34bfa 100755 --- a/cli.py +++ b/cli.py @@ -12,7 +12,8 @@ def main() -> None: """ parser = argparse.ArgumentParser(description='Break One-Time Pad Encryption with key reuse') parser.add_argument('file', type=str, help='file containing hexadecimal ciphertexts, delimited by new lines') - parser.add_argument('-o', type=str, dest='output_file', default='result.json', help='filename to export decryptions to') + parser.add_argument('-o', type=str, dest='output_file', default='result.json', help='filename to export ' + 'decryptions to') args = parser.parse_args() with open(args.file, 'r') as f: @@ -21,6 +22,10 @@ def main() -> None: try: ciphertexts = list(map(bytearray.fromhex, ciphertexts)) except ValueError as error: - sys.exit("Invalid hexadecimal: {error}") + sys.exit(f"Invalid hexadecimal: {error}") many_time_pad_attack(ciphertexts, args.output_file) + + +if __name__ == "__main__": + main() diff --git a/manytime/analysis.py b/manytime/analysis.py index e4dc14d..5d6546b 100644 --- a/manytime/analysis.py +++ b/manytime/analysis.py @@ -45,9 +45,10 @@ def recover_partial_key(ciphertexts: Iterable[bytearray]) -> List[Optional[int]] # Although we know it is a space we don't know which ciphertext it came from main_counter.update(track_spaces(xor(main_ciphertext, secondary_ciphertext))) - # Now we have tracked all the possible spaces we have seen, anchored to the index of the main ciphertext where we saw it - # Therefore, if we have seen a space len(ciphertexts) - 1 times in a certain position, we know that because it was present - # when XORd with each ciphertext, that it must have come from the main_ciphertext. Meaning, that position is a space in the main_plaintext + # Now we have tracked all the possible spaces we have seen, anchored to the index of the main ciphertext + # where we saw it Therefore, if we have seen a space len(ciphertexts) - 1 times in a certain position, + # we know that because it was present when XORd with each ciphertext, that it must have come from the + # main_ciphertext. Meaning, that position is a space in the main_plaintext for index, count in main_counter.items(): if count == len(ciphertexts) - 1: key[index] = ord(' ') ^ main_ciphertext[index] diff --git a/manytime/interactive.py b/manytime/interactive.py index 0659b27..8410b82 100644 --- a/manytime/interactive.py +++ b/manytime/interactive.py @@ -4,7 +4,6 @@ import json import urwid -from enum import Enum from manytime import keys @@ -13,7 +12,8 @@ from typing import Iterable, Optional, Tuple, NoReturn, Union, Any, List -def partial_decrypt(key: Key, ciphertext: bytearray, unknown_character: Union[Tuple[str, str], str] = ('unknown', '_')) -> Iterable: +def partial_decrypt(key: Key, ciphertext: bytearray, + unknown_character: Union[Tuple[str, str], str] = ('unknown', '_')) -> Iterable: """ Decrypt ciphertext using key Decrypting a letter using an unknown key element will result in unknown_character @@ -61,7 +61,8 @@ def _edit_decryption(self, letter: str) -> None: # Letters which are longer, for example "shift left" are not key presses we want to deal with. # All special characters are handled elsewhere, this code handles letters and delete keys # therefore we ignore all others. - self.application.key[index] = None if letter in self.REMOVE_KEYS or len(letter) > 1 else ord(letter) ^ ciphertext[index] + self.application.key[index] = None if (letter in self.REMOVE_KEYS or + len(letter) > 1) else ord(letter) ^ ciphertext[index] # Update all decryptions new_decryptions = [partial_decrypt(self.application.key, c) for c in self.application.ciphertexts] diff --git a/manytime/models.py b/manytime/models.py index 318679b..0be3bd8 100644 --- a/manytime/models.py +++ b/manytime/models.py @@ -7,7 +7,7 @@ from manytime import keys -from typing import Optional, List, Tuple, Union, Iterator +from typing import Optional, List, Tuple, Union, Iterator, Any class Key: