Source code for src.ch04.practice.p1_hack_lincoln

"""Hack route cipher sent by Abraham Lincoln."""
from itertools import combinations
from src.ch03.c1_anagram_generator import split


[docs]def get_factors(integer: int) -> list: """Get factors of integer. Calculate factors of a given integer. Args: integer (int): Number to get factors of. Returns: List of integer factors of **integer**. """ result = [] # A factor will always be less than or equal to sqrt(integer). for i in range(1, int(integer ** 0.5) + 1): if integer % i == 0: result.append(i) # If you have one factor, the other is integer / factor result.append(integer // i) return sorted(list(set(result))) # Eliminate perfect squares
[docs]def keygen(length: int) -> list: """Generate all possible route cipher keys. Generates a list of all possible route cipher keys of **length**. Args: length (int): Length of route cipher key. Returns: List of lists of integers representing all possible route cipher keys of **length**. Example: >>> from src.ch04.practice.p1_hack_lincoln import keygen >>> keygen(2) [[-1, -2], [-1, 2], [1, -2], [1, 2]] """ result = [] master_key = range(1, length + 1) # Get all possible combinations of direction (pos/neg) of length combs = set(combinations([-1, 1] * length, length)) # Remove repeats for comb in combs: result.append([sign * key for sign, key in zip(comb, master_key)]) result.sort() # Sort for test consistency. return result
[docs]def decode_route(keys: list, cipherlist: list) -> list: """Decode route cipher. Decode **cipherlist** encoded with a route cipher using **keys**. Args: keys (list): List of signed, integer keys. cipherlist (list): List of strings representing encoded message. Returns: List of strings representing plaintext message. Note: Assumes vertical encoding route. """ table, message = [], [] split_list = split(cipherlist, len(keys)) rows = len(split_list[0]) # Build translation table. for key in keys: if key < 0: # If negative, reverse direction split_list[0].reverse() table.append(split_list[0]) del split_list[0] # For each column in the table, copy the relevant row. for row in range(rows): for column in table: message.append(column[row]) return message
[docs]def hack_route(ciphertext: str) -> None: """Hack route cipher. Hack route cipher by using :func:`get_factors` to find all possible key lengths. Then use :func:`keygen` to generate all possible keys and pass each one through :func:`decode_route`. Args: ciphertext (str): Message encoded with route cipher. Returns: None. Prints all possible decoded messages. """ cipherlist = ciphertext.split() # Get all possible key lengths. factors = get_factors(len(cipherlist)) for factor in factors: # Get all possible keys. if any([factor == 1, factor == len(cipherlist)]): # Key length of 1 is the full cipherlist and key length of # cipherlist length is one word per column. continue keys = keygen(factor) for key in keys: # Use each key to decode route cipher. message = ' '.join(decode_route(key, cipherlist)) print(f'Key: {key}\nDecoded message: {message}\n')
[docs]def main(): """Demonstrate hack of Lincoln's route cipher.""" print('I can do a brute-force hack of a route cipher sent by ' 'Abraham Lincoln,\nand I do a better job than he did in that dumb ' 'zombie movie.') print('\nNote: I only hack the route cipher. I leave the ' 'word-transposition\ncipher to you and your biochemical brain.\n') ciphertext = """THIS OFF DETAINED ASCERTAIN WAYLAND CORRESPONDENTS OF AT WHY AND IF FILLS IT YOU GET THEY NEPTUNE THE TRIBUNE PLEASE ARE THEM CAN UP""" print(f'Hacking: {ciphertext}\n') hack_route(ciphertext)
if __name__ == '__main__': main()