Module szyfrow.amsco
+Enciphering and deciphering using 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".
++Expand source code +
+"""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 *
+
+__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_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_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_encipher(message, keyword,
+ fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
+ """AMSCO transposition encipher.
+
+ >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2))
+ 'hoteelhler'
+ >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1))
+ 'hetelhelor'
+ >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2))
+ 'hotelerelh'
+ >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1))
+ 'hetelorlhe'
+ >>> amsco_encipher('hereissometexttoencipher', 'encode')
+ 'etecstthhomoerereenisxip'
+ >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2))
+ 'hetcsoeisterereipexthomn'
+ >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hecsoisttererteipexhomen'
+ >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1))
+ 'heecisoosttrrtepeixhemen'
+ >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2))
+ 'hxtomephescieretoeisnter'
+ >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hxomeiphscerettoisenteer'
+ """
+ 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_decipher(message, keyword,
+ fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row):
+ """AMSCO transposition decipher
+
+ >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2))
+ 'hellothere'
+ >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1))
+ 'hellothere'
+ >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2))
+ 'hellothere'
+ >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1))
+ 'hellothere'
+ >>> amsco_decipher('etecstthhomoerereenisxip', 'encode')
+ 'hereissometexttoencipher'
+ >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2))
+ 'hereissometexttoencipher'
+ >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hereissometexttoencipher'
+ >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1))
+ 'hereissometexttoencipher'
+ >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2))
+ 'hereissometexttoencipher'
+ >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous)
+ 'hereissometexttoencipher'
+ """
+
+ 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)
+ 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=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.
+
+ If `translist` is not specified, use
+ [`szyfrow.support.langauge_models.transpositions`](support/language_models.html#szyfrow.support.language_models.transpositions).
+
+ >>> 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 \
+ 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), <AmscoFillStyle.continuous: 1>), -709.4646722...)
+ >>> 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 \
+ 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), <AmscoFillStyle.continuous: 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
+ 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_decipher(message, transposition,
+ fillpattern=pattern, fillstyle=fillstyle)
+ fit = fitness(sanitise(plaintext))
+ return (transposition, pattern, fillstyle), fit
+
+if __name__ == "__main__":
+ import doctest
+Functions
+-
+
+def amsco_break(message, translist=None, patterns=[(1, 2), (2, 1)], fillstyles=[<AmscoFillStyle.continuous: 1>, <AmscoFillStyle.same_each_row: 2>, <AmscoFillStyle.reverse_each_row: 3>], fitness=<function Pbigrams>, chunksize=500) +
+-
++
Breaks an AMSCO transposition cipher using a dictionary and +n-gram frequency analysis.
+If
+translist
is not specified, use +szyfrow.support.langauge_models.transpositions
.>>> 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 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), <AmscoFillStyle.continuous: 1>), -709.4646722...) +>>> 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 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), <AmscoFillStyle.continuous: 1>), -997.0129085...) +
+++Expand source code +
+
+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. + + If `translist` is not specified, use + [`szyfrow.support.langauge_models.transpositions`](support/language_models.html#szyfrow.support.language_models.transpositions). + + >>> 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 \ + 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), <AmscoFillStyle.continuous: 1>), -709.4646722...) + >>> 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 \ + 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), <AmscoFillStyle.continuous: 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 + 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) +
+-
+
+++
+Expand source code +
+
+def amsco_break_worker(message, transposition, + pattern, fillstyle, fitness): + plaintext = amsco_decipher(message, transposition, + fillpattern=pattern, fillstyle=fillstyle) + fit = fitness(sanitise(plaintext)) + return (transposition, pattern, fillstyle), fit
+ +def amsco_decipher(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.reverse_each_row) +
+-
++
AMSCO transposition decipher
+>>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) +'hellothere' +>>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) +'hellothere' +>>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) +'hellothere' +>>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) +'hellothere' +>>> amsco_decipher('etecstthhomoerereenisxip', 'encode') +'hereissometexttoencipher' +>>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) +'hereissometexttoencipher' +>>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) +'hereissometexttoencipher' +>>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) +'hereissometexttoencipher' +>>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) +'hereissometexttoencipher' +>>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) +'hereissometexttoencipher' +
+++Expand source code +
+
+def amsco_decipher(message, keyword, + fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): + """AMSCO transposition decipher + + >>> amsco_decipher('hoteelhler', 'abc', fillpattern=(1, 2)) + 'hellothere' + >>> amsco_decipher('hetelhelor', 'abc', fillpattern=(2, 1)) + 'hellothere' + >>> amsco_decipher('hotelerelh', 'acb', fillpattern=(1, 2)) + 'hellothere' + >>> amsco_decipher('hetelorlhe', 'acb', fillpattern=(2, 1)) + 'hellothere' + >>> amsco_decipher('etecstthhomoerereenisxip', 'encode') + 'hereissometexttoencipher' + >>> amsco_decipher('hetcsoeisterereipexthomn', 'cipher', fillpattern=(1, 2)) + 'hereissometexttoencipher' + >>> amsco_decipher('hecsoisttererteipexhomen', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + 'hereissometexttoencipher' + >>> amsco_decipher('heecisoosttrrtepeixhemen', 'cipher', fillpattern=(2, 1)) + 'hereissometexttoencipher' + >>> amsco_decipher('hxtomephescieretoeisnter', 'cipher', fillpattern=(1, 3, 2)) + 'hereissometexttoencipher' + >>> amsco_decipher('hxomeiphscerettoisenteer', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + 'hereissometexttoencipher' + """ + + 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) + 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_encipher(message, keyword, fillpattern=(1, 2), fillstyle=AmscoFillStyle.reverse_each_row) +
+-
++
AMSCO transposition encipher.
+>>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2)) +'hoteelhler' +>>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1)) +'hetelhelor' +>>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2)) +'hotelerelh' +>>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1)) +'hetelorlhe' +>>> amsco_encipher('hereissometexttoencipher', 'encode') +'etecstthhomoerereenisxip' +>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) +'hetcsoeisterereipexthomn' +>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) +'hecsoisttererteipexhomen' +>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) +'heecisoosttrrtepeixhemen' +>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) +'hxtomephescieretoeisnter' +>>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) +'hxomeiphscerettoisenteer' +
+++Expand source code +
+
+def amsco_encipher(message, keyword, + fillpattern=(1,2), fillstyle=AmscoFillStyle.reverse_each_row): + """AMSCO transposition encipher. + + >>> amsco_encipher('hellothere', 'abc', fillpattern=(1, 2)) + 'hoteelhler' + >>> amsco_encipher('hellothere', 'abc', fillpattern=(2, 1)) + 'hetelhelor' + >>> amsco_encipher('hellothere', 'acb', fillpattern=(1, 2)) + 'hotelerelh' + >>> amsco_encipher('hellothere', 'acb', fillpattern=(2, 1)) + 'hetelorlhe' + >>> amsco_encipher('hereissometexttoencipher', 'encode') + 'etecstthhomoerereenisxip' + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2)) + 'hetcsoeisterereipexthomn' + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 2), fillstyle=AmscoFillStyle.continuous) + 'hecsoisttererteipexhomen' + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(2, 1)) + 'heecisoosttrrtepeixhemen' + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2)) + 'hxtomephescieretoeisnter' + >>> amsco_encipher('hereissometexttoencipher', 'cipher', fillpattern=(1, 3, 2), fillstyle=AmscoFillStyle.continuous) + 'hxomeiphscerettoisenteer' + """ + 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_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_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)]] +
+++Expand source code +
+
+def amsco_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_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 cat(iterable, /) +
+-
++
Concatenate any number of strings.
+The string whose method is called is inserted in between each given string. +The result is returned as a new string.
+Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
+ +def lcat(iterable, /) +
+-
++
Concatenate any number of strings.
+The string whose method is called is inserted in between each given string. +The result is returned as a new string.
+Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
+ +def wcat(iterable, /) +
+-
++
Concatenate any number of strings.
+The string whose method is called is inserted in between each given string. +The result is returned as a new string.
+Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
+
Classes
+-
+
+class AmscoFillStyle +(value, names=None, *, module=None, qualname=None, type=None, start=1) +
+-
++
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+++Expand source code +
+
+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
Ancestors
+-
+
- enum.Enum +
Class variables
+-
+
var continuous
+- + + +
var reverse_each_row
+- + + +
var same_each_row
+- + + +
+ +class AmscoSlice +(index, start, end) +
+-
++
Where each piece of plainatext ends up in the AMSCO +transpositon cipher.
Ancestors
+-
+
- builtins.tuple +
Instance variables
+-
+
var end
+-
++
Where the slice ends in the plaintext
+ var index
+-
++
Where the slice appears in the plaintext
+ var start
+-
++
Where the slice starts in the plaintext
+
+