X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=szyfrow%2Famsco.py;h=3372569a3ae6a1ef1b2b04602054870f37d9f72b;hb=refs%2Fheads%2Fmain;hp=48ab30627b7c46f472b526a2fcbd4cfbe3c05b6c;hpb=27c8005f6dea0026887b80a01b5f93a8f1b3c2b2;p=szyfrow.git diff --git a/szyfrow/amsco.py b/szyfrow/amsco.py index 48ab306..3372569 100644 --- a/szyfrow/amsco.py +++ b/szyfrow/amsco.py @@ -1,22 +1,60 @@ +"""Enciphering and deciphering using the [Amsco cipher](http://ericbrandel.com/2016/10/09/the-amsco-cipher/). +Also attempts to break messages that use an Amsco cipher. + +The Amsco cipher is a column transpositoin cipher. The plaintext is laid out, +row by row, into columns. However, different numbers of letters are laid out +in each cell, typically in a 1-2 pattern. + +It's clearer with an example. Consider we're using the keyword "perceptive", +which turns into "perctiv". The text ""It is a truth universally +acknowledged, that a single man in, possession of a good fortune, must be in +want of a wife." is laid out in seven columns like this: + + p e r c t i v + -------------------- + i ti s at r ut h + un i ve r sa l ly + a ck n ow l ed g + ed t ha t as i ng + l em a ni n po s + se s si o no f ag + o od f or t un e + mu s tb e in w an + t of a wi f e + +The ciphertext is read out in columns, according to the order of the keyword. +In this example, the "c" column is read first, then the "e" column, and so on. +That gives the ciphertext of "atrowtnioorewi tiicktemsodsof utledipofunwe +iunaedlseomut svenhaasiftba rsalasnnotinf hlygngsagean". +""" + from enum import Enum import multiprocessing import itertools from szyfrow.support.utilities import * from szyfrow.support.language_models import * -from szyfrow.column_transposition import transpositions, transpositions_of -# 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' +__pdoc__ = {} + AmscoSlice = collections.namedtuple('AmscoSlice', ['index', 'start', 'end']) +__pdoc__['AmscoSlice'] = """Where each piece of plainatext ends up in the AMSCO +transpositon cipher.""" +__pdoc__['AmscoSlice.index'] = """Where the slice appears in the plaintext""" +__pdoc__['AmscoSlice.start'] = """Where the slice starts in the plaintext""" +__pdoc__['AmscoSlice.end'] = """Where the slice ends in the plaintext""" class AmscoFillStyle(Enum): + """Different methods of filling the grid. + * `continuous`: continue the fillpattern unbroken by row boundaries + * `same_each_row`: each row has the same fillpattern + * `reverse_each_row`: each row has the reversed fillpattern to the row above + """ continuous = 1 same_each_row = 2 reverse_each_row = 3 -def amsco_transposition_positions(message, keyword, +def amsco_positions(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous, fillcolumnwise=False, @@ -25,7 +63,7 @@ def amsco_transposition_positions(message, keyword, 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', \ + >>> amsco_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), @@ -72,64 +110,64 @@ def amsco_transposition_positions(message, keyword, current_fillpattern = list(reversed(current_fillpattern)) return [transpose(r, transpositions) for r in grid] -def amsco_transposition_encipher(message, keyword, +def amsco_encipher(message, keyword, fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): """AMSCO transposition encipher. - >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(1, 2)) + >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2)) 'hoteelhler' - >>> amsco_transposition_encipher('hellothere', 'abc', fillpattern=(2, 1)) + >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1)) 'hetelhelor' - >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(1, 2)) + >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2)) 'hotelerelh' - >>> amsco_transposition_encipher('hellothere', 'acb', fillpattern=(2, 1)) + >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1)) 'hetelorlhe' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'encode') + >>> amsco_encipher('hereissometexttoencipher', 'encode') 'etecstthhomoerereenisxip' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) 'hetcsoeisterereipexthomn' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) 'hecsoisttererteipexhomen' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) 'heecisoosttrrtepeixhemen' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) 'hxtomephescieretoeisnter' - >>> amsco_transposition_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) 'hxomeiphscerettoisenteer' """ - grid = amsco_transposition_positions(message, keyword, + grid = amsco_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, +def amsco_decipher(message, keyword, fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): """AMSCO transposition decipher - >>> amsco_transposition_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) + >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) 'hellothere' - >>> amsco_transposition_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) + >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) 'hellothere' - >>> amsco_transposition_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) + >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) 'hellothere' - >>> amsco_transposition_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) + >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) 'hellothere' - >>> amsco_transposition_decipher('etecstthhomoerereenisxip', 'encode') + >>> amsco_decipher('etecstthhomoerereenisxip', 'encode') 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) + >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) + >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) + >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) 'hereissometexttoencipher' - >>> amsco_transposition_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) 'hereissometexttoencipher' """ - grid = amsco_transposition_positions(message, keyword, + grid = amsco_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) @@ -140,16 +178,19 @@ def amsco_transposition_decipher(message, keyword, return cat(plaintext_list) -def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], +def amsco_break(message, translist=None, 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 + n-gram frequency analysis. + + If `translist` is not specified, use + [`szyfrow.support.langauge_models.transpositions`](support/language_models.html#szyfrow.support.language_models.transpositions). - >>> amsco_break(amsco_transposition_encipher(sanitise( \ + >>> amsco_break(amsco_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 \ @@ -162,7 +203,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], (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( \ + >>> amsco_break(amsco_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 \ @@ -176,6 +217,9 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], patterns=[(1, 2), (2, 1)], fitness=Ptrigrams) # doctest: +ELLIPSIS (((2, 0, 5, 3, 1, 4, 6), (2, 1), ), -997.0129085...) """ + if translist is None: + translist = transpositions + with multiprocessing.Pool() as pool: helper_args = [(message, trans, pattern, fillstyle, fitness) for trans in translist @@ -188,7 +232,7 @@ def amsco_break(message, translist=transpositions, patterns = [(1, 2), (2, 1)], def amsco_break_worker(message, transposition, pattern, fillstyle, fitness): - plaintext = amsco_transposition_decipher(message, transposition, + plaintext = amsco_decipher(message, transposition, fillpattern=pattern, fillstyle=fillstyle) fit = fitness(sanitise(plaintext)) return (transposition, pattern, fillstyle), fit