+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), <AmscoFillStyle.continuous: 1>), -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), <AmscoFillStyle.continuous: 1>), -997.0129085...)
+ """
+ with 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
+
+
+def hill_break(message, matrix_size=2, fitness=Pletters,
+ number_of_solutions=1, chunksize=500):
+
+ all_matrices = [np.matrix(list(m))
+ for m in itertools.product([list(r)
+ for r in itertools.product(range(26), repeat=matrix_size)],
+ repeat=matrix_size)]
+ valid_matrices = [m for m, d in
+ zip(all_matrices, (int(round(linalg.det(m))) for m in all_matrices))
+ if d != 0
+ if d % 2 != 0
+ if d % 13 != 0 ]
+ with Pool() as pool:
+ helper_args = [(message, matrix, fitness)
+ for matrix in valid_matrices]
+ # Gotcha: the helper function here needs to be defined at the top level
+ # (limitation of Pool.starmap)
+ breaks = pool.starmap(hill_break_worker, helper_args, chunksize)
+ if number_of_solutions == 1:
+ return max(breaks, key=lambda k: k[1])
+ else:
+ return sorted(breaks, key=lambda k: k[1], reverse=True)[:number_of_solutions]
+
+def hill_break_worker(message, matrix, fitness):
+ plaintext = hill_decipher(matrix, message)
+ fit = fitness(plaintext)
+ logger.debug('Hill cipher break attempt using key {0} gives fit of '
+ '{1} and decrypt starting: {2}'.format(matrix,
+ fit, sanitise(plaintext)[:50]))
+ return matrix, fit
+