X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=cipher.py;h=83700a538cec2cd55f01b390967565e21e7afbd3;hb=8c4e8509ebd603f878a844b7cec8d0e2375ec8f9;hp=6cce72543224f02468e8a7609b674d158d238127;hpb=674b12c1ed3d8c4a6007f1744afb7a5fbf90fb6c;p=cipher-tools.git diff --git a/cipher.py b/cipher.py index 6cce725..83700a5 100644 --- a/cipher.py +++ b/cipher.py @@ -2,7 +2,10 @@ import string import collections import math from enum import Enum -from itertools import zip_longest, cycle, chain +from itertools import zip_longest, cycle, chain, count +import numpy as np +from numpy import matrix +from numpy import linalg from language_models import * @@ -629,6 +632,236 @@ def railfence_decipher(message, height, fillvalue=''): up_rows.reverse() return ''.join(c for r in zip_longest(*(down_rows + up_rows), fillvalue='') for c in r) +def make_cadenus_keycolumn(doubled_letters = 'vw', start='a', reverse=False): + """Makes the key column for a Cadenus cipher (the column down between the + rows of letters) + + >>> make_cadenus_keycolumn()['a'] + 0 + >>> make_cadenus_keycolumn()['b'] + 1 + >>> make_cadenus_keycolumn()['c'] + 2 + >>> make_cadenus_keycolumn()['v'] + 21 + >>> make_cadenus_keycolumn()['w'] + 21 + >>> make_cadenus_keycolumn()['z'] + 24 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['a'] + 1 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['b'] + 0 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['c'] + 24 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['i'] + 18 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['j'] + 18 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['v'] + 6 + >>> make_cadenus_keycolumn(doubled_letters='ij', start='b', reverse=True)['z'] + 2 + """ + index_to_remove = string.ascii_lowercase.find(doubled_letters[0]) + short_alphabet = string.ascii_lowercase[:index_to_remove] + string.ascii_lowercase[index_to_remove+1:] + if reverse: + short_alphabet = ''.join(reversed(short_alphabet)) + start_pos = short_alphabet.find(start) + rotated_alphabet = short_alphabet[start_pos:] + short_alphabet[:start_pos] + keycolumn = {l: i for i, l in enumerate(rotated_alphabet)} + keycolumn[doubled_letters[0]] = keycolumn[doubled_letters[1]] + return keycolumn + +def cadenus_encipher(message, keyword, keycolumn, fillvalue='a'): + """Encipher with the Cadenus cipher + + >>> cadenus_encipher(sanitise('Whoever has made a voyage up the Hudson ' \ + 'must remember the Kaatskill mountains. ' \ + 'They are a dismembered branch of the great'), \ + 'wink', \ + make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True)) + 'antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaasuvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned' + >>> cadenus_encipher(sanitise('a severe limitation on the usefulness of ' \ + 'the cadenus is that every message must be ' \ + 'a multiple of twenty-five letters long'), \ + 'easy', \ + make_cadenus_keycolumn(doubled_letters='vw', start='a', reverse=True)) + 'systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtofarenuseieeieltarlmentieetogevesitfaisltngeeuvowul' + """ + rows = chunks(message, len(message) // 25, fillvalue=fillvalue) + columns = zip(*rows) + rotated_columns = [col[start:] + col[:start] for start, col in zip([keycolumn[l] for l in keyword], columns)] + rotated_rows = zip(*rotated_columns) + transpositions = transpositions_of(keyword) + transposed = [transpose(r, transpositions) for r in rotated_rows] + return ''.join(chain(*transposed)) + +def cadenus_decipher(message, keyword, keycolumn, fillvalue='a'): + """ + >>> cadenus_decipher('antodeleeeuhrsidrbhmhdrrhnimefmthgeaetakseomehetyaa' \ + 'suvoyegrastmmuuaeenabbtpchehtarorikswosmvaleatned', \ + 'wink', \ + make_cadenus_keycolumn(reverse=True)) + 'whoeverhasmadeavoyageupthehudsonmustrememberthekaatskillmountainstheyareadismemberedbranchofthegreat' + >>> cadenus_decipher('systretomtattlusoatleeesfiyheasdfnmschbhneuvsnpmtof' \ + 'arenuseieeieltarlmentieetogevesitfaisltngeeuvowul', \ + 'easy', \ + make_cadenus_keycolumn(reverse=True)) + 'aseverelimitationontheusefulnessofthecadenusisthateverymessagemustbeamultipleoftwentyfiveletterslong' + """ + rows = chunks(message, len(message) // 25, fillvalue=fillvalue) + transpositions = transpositions_of(keyword) + untransposed_rows = [untranspose(r, transpositions) for r in rows] + columns = zip(*untransposed_rows) + rotated_columns = [col[-start:] + col[:-start] for start, col in zip([keycolumn[l] for l in keyword], columns)] + rotated_rows = zip(*rotated_columns) + # return rotated_columns + return ''.join(chain(*rotated_rows)) + + +def hill_encipher(matrix, message_letters, fillvalue='a'): + """Hill cipher + + >>> hill_encipher(np.matrix([[7,8], [11,11]]), 'hellothere') + 'drjiqzdrvx' + >>> hill_encipher(np.matrix([[6, 24, 1], [13, 16, 10], [20, 17, 15]]), \ + 'hello there') + 'tfjflpznvyac' + """ + n = len(matrix) + sanitised_message = sanitise(message_letters) + if len(sanitised_message) % n != 0: + padding = fillvalue[0] * (n - len(sanitised_message) % n) + else: + padding = '' + message = [ord(c) - ord('a') for c in sanitised_message + padding] + message_chunks = [message[i:i+n] for i in range(0, len(message), n)] + # message_chunks = chunks(message, len(matrix), fillvalue=None) + enciphered_chunks = [((matrix * np.matrix(c).T).T).tolist()[0] + for c in message_chunks] + return ''.join([chr(int(round(l)) % 26 + ord('a')) + for l in sum(enciphered_chunks, [])]) + +def hill_decipher(matrix, message, fillvalue='a'): + """Hill cipher + + >>> hill_decipher(np.matrix([[7,8], [11,11]]), 'drjiqzdrvx') + 'hellothere' + >>> hill_decipher(np.matrix([[6, 24, 1], [13, 16, 10], [20, 17, 15]]), \ + 'tfjflpznvyac') + 'hellothereaa' + """ + adjoint = linalg.det(matrix)*linalg.inv(matrix) + inverse_determinant = modular_division_table[int(round(linalg.det(matrix))) % 26][1] + inverse_matrix = (inverse_determinant * adjoint) % 26 + return hill_encipher(inverse_matrix, message, fillvalue) + + +# Where each piece of text ends up in the AMSCO transpositon cipher. +# 'index' shows where the slice appears in the plaintext, with the slice +# from 'start' to 'end' +AmscoSlice = collections.namedtuple('AmscoSlice', ['index', 'start', 'end']) + +def amsco_transposition_positions(message, keyword, + fillpattern=(1, 2), + fillcolumnwise=False, + emptycolumnwise=True): + """Creates the grid for the AMSCO transposition cipher. Each element in the + grid shows the index of that slice and the start and end positions of the + plaintext that go to make it up. + + >>> amsco_transposition_positions(string.ascii_lowercase, 'freddy', \ + fillpattern=(1, 2)) # doctest: +NORMALIZE_WHITESPACE + [[AmscoSlice(index=3, start=4, end=6), + AmscoSlice(index=2, start=3, end=4), + AmscoSlice(index=0, start=0, end=1), + AmscoSlice(index=1, start=1, end=3), + AmscoSlice(index=4, start=6, end=7)], + [AmscoSlice(index=8, start=12, end=13), + AmscoSlice(index=7, start=10, end=12), + AmscoSlice(index=5, start=7, end=9), + AmscoSlice(index=6, start=9, end=10), + AmscoSlice(index=9, start=13, end=15)], + [AmscoSlice(index=13, start=19, end=21), + AmscoSlice(index=12, start=18, end=19), + AmscoSlice(index=10, start=15, end=16), + AmscoSlice(index=11, start=16, end=18), + AmscoSlice(index=14, start=21, end=22)], + [AmscoSlice(index=18, start=27, end=28), + AmscoSlice(index=17, start=25, end=27), + AmscoSlice(index=15, start=22, end=24), + AmscoSlice(index=16, start=24, end=25), + AmscoSlice(index=19, start=28, end=30)]] + """ + transpositions = transpositions_of(keyword) + fill_iterator = cycle(fillpattern) + indices = count() + message_length = len(message) + + current_position = 0 + grid = [] + while current_position < message_length: + row = [] + for _ in range(len(transpositions)): + index = next(indices) + gap = next(fill_iterator) + row += [AmscoSlice(index, current_position, current_position + gap)] + current_position += gap + grid += [row] + return [transpose(r, transpositions) for r in grid] + +def amsco_transposition_encipher(message, keyword, fillpattern=(1,2)): + """AMSCO transposition encipher. + + >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(1, 2)) + 'hoteelhler' + >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(2, 1)) + 'hetelhelor' + >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(1, 2)) + 'hotelerelh' + >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(2, 1)) + 'hetelorlhe' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) + 'hecsoisttererteipexhomen' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) + 'heetcisooestrrepeixthemn' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) + 'hxomeiphscerettoisenteer' + """ + grid = amsco_transposition_positions(message, keyword, fillpattern=fillpattern) + ct_as_grid = [[message[s.start:s.end] for s in r] for r in grid] + return combine_every_nth(ct_as_grid) + + +def amsco_transposition_decipher(message, keyword, fillpattern=(1,2)): + """AMSCO transposition decipher + + >>> amsco_transposition_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) + 'hellothere' + >>> amsco_transposition_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) + 'hellothere' + >>> amsco_transposition_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) + 'hellothere' + >>> amsco_transposition_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) + 'hellothere' + >>> amsco_transposition_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2)) + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('heetcisooestrrepeixthemn', 'cipher', fillpattern=(2, 1)) + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2)) + 'hereissometexttoencipher' + """ + + grid = amsco_transposition_positions(message, keyword, fillpattern=fillpattern) + transposed_sections = [s for c in [l for l in zip(*grid)] for s in c] + plaintext_list = [''] * len(transposed_sections) + current_pos = 0 + for slice in transposed_sections: + plaintext_list[slice.index] = message[current_pos:current_pos-slice.start+slice.end][:len(message[slice.start:slice.end])] + current_pos += len(message[slice.start:slice.end]) + return ''.join(plaintext_list) + class PocketEnigma(object): """A pocket enigma machine