X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;ds=sidebyside;f=amsco.py;fp=amsco.py;h=4eeeb5961cde67a55a6b28229dfccd729a8d9bcb;hb=3d8f7067b9c3a48ef140d7cff834d18ee91f58b3;hp=0000000000000000000000000000000000000000;hpb=21c390a77d42729afa23844ef2f1295106bed3de;p=cipher-tools.git diff --git a/amsco.py b/amsco.py new file mode 100644 index 0000000..4eeeb59 --- /dev/null +++ b/amsco.py @@ -0,0 +1,204 @@ +from enum import Enum +import multiprocessing +import itertools + +from utilities import * +from language_models import * +from column_transposition import transpositions + +from logger import logger + +# 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']) + +class AmscoFillStyle(Enum): + continuous = 1 + same_each_row = 2 + reverse_each_row = 3 + +def amsco_transposition_positions(message, keyword, + fillpattern=(1, 2), + fillstyle=AmscoFillStyle.continuous, + 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 = itertools.cycle(fillpattern) + indices = itertools.count() + message_length = len(message) + + current_position = 0 + grid = [] + current_fillpattern = fillpattern + while current_position < message_length: + row = [] + if fillstyle == AmscoFillStyle.same_each_row: + fill_iterator = itertools.cycle(fillpattern) + if fillstyle == AmscoFillStyle.reverse_each_row: + fill_iterator = itertools.cycle(current_fillpattern) + 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] + if fillstyle == AmscoFillStyle.reverse_each_row: + current_fillpattern = list(reversed(current_fillpattern)) + return [transpose(r, transpositions) for r in grid] + +def amsco_transposition_encipher(message, keyword, + fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): + """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', 'encode') + 'etecstthhomoerereenisxip' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) + 'hetcsoeisterereipexthomn' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + 'hecsoisttererteipexhomen' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) + 'heecisoosttrrtepeixhemen' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) + 'hxtomephescieretoeisnter' + >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + 'hxomeiphscerettoisenteer' + """ + grid = amsco_transposition_positions(message, keyword, + fillpattern=fillpattern, fillstyle=fillstyle) + 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), fillstyle=AmscoFillStyle.reverse_each_row): + """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('etecstthhomoerereenisxip', 'encode') + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) + 'hereissometexttoencipher' + >>> amsco_transposition_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + 'hereissometexttoencipher' + """ + + grid = amsco_transposition_positions(message, keyword, + fillpattern=fillpattern, fillstyle=fillstyle) + 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 cat(plaintext_list) + + +def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], + fillstyles = [AmscoFillStyle.continuous, + AmscoFillStyle.same_each_row, + AmscoFillStyle.reverse_each_row], + fitness=Pbigrams, + chunksize=500): + """Breaks an AMSCO transposition cipher using a dictionary and + n-gram frequency analysis + + >>> amsco_break(amsco_transposition_encipher(sanitise( \ + "It is a truth universally acknowledged, that a single man in \ + possession of a good fortune, must be in want of a wife. However \ + little known the feelings or views of such a man may be on his \ + first entering a neighbourhood, this truth is so well fixed in \ + the minds of the surrounding families, that he is considered the \ + rightful property of some one or other of their daughters."), \ + 'encipher'), \ + translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \ + (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \ + (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \ + patterns=[(1, 2)]) # doctest: +ELLIPSIS + (((2, 0, 5, 3, 1, 4, 6), (1, 2), ), -709.4646722...) + >>> amsco_break(amsco_transposition_encipher(sanitise( \ + "It is a truth universally acknowledged, that a single man in \ + possession of a good fortune, must be in want of a wife. However \ + little known the feelings or views of such a man may be on his \ + first entering a neighbourhood, this truth is so well fixed in \ + the minds of the surrounding families, that he is considered the \ + rightful property of some one or other of their daughters."), \ + 'encipher', fillpattern=(2, 1)), \ + translist={(2, 0, 5, 3, 1, 4, 6): ['encipher'], \ + (5, 0, 6, 1, 3, 4, 2): ['fourteen'], \ + (6, 1, 0, 4, 5, 3, 2): ['keyword']}, \ + patterns=[(1, 2), (2, 1)], fitness=Ptrigrams) # doctest: +ELLIPSIS + (((2, 0, 5, 3, 1, 4, 6), (2, 1), ), -997.0129085...) + """ + with multiprocessing.Pool() as pool: + helper_args = [(message, trans, pattern, fillstyle, fitness) + for trans in translist + for pattern in patterns + for fillstyle in fillstyles] + # Gotcha: the helper function here needs to be defined at the top level + # (limitation of Pool.starmap) + breaks = pool.starmap(amsco_break_worker, helper_args, chunksize) + return max(breaks, key=lambda k: k[1]) + +def amsco_break_worker(message, transposition, + pattern, fillstyle, fitness): + plaintext = amsco_transposition_decipher(message, transposition, + fillpattern=pattern, fillstyle=fillstyle) + fit = fitness(sanitise(plaintext)) + logger.debug('AMSCO transposition break attempt using key {0} and pattern' + '{1} ({2}) gives fit of {3} and decrypt starting: ' + '{4}'.format( + transposition, pattern, fillstyle, fit, + sanitise(plaintext)[:50])) + return (transposition, pattern, fillstyle), fit + +if __name__ == "__main__": + import doctest \ No newline at end of file