+def scytale_break_mp(message, max_key_length=20,
+ fitness=Pbigrams, chunksize=500):
+ """Breaks a scytale cipher using a range of lengths and
+ n-gram frequency analysis
+
+ >>> scytale_break_mp(scytale_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."), \
+ 5)) # doctest: +ELLIPSIS
+ (5, -709.4646722...)
+ >>> scytale_break_mp(scytale_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."), \
+ 5), \
+ fitness=Ptrigrams) # doctest: +ELLIPSIS
+ (5, -997.0129085...)
+ """
+ with Pool() as pool:
+ helper_args = [(message, trans, False, True, fitness)
+ for trans in
+ [[col for col in range(math.ceil(len(message)/rows))]
+ for rows in range(1,max_key_length+1)]]
+ # Gotcha: the helper function here needs to be defined at the top level
+ # (limitation of Pool.starmap)
+ breaks = pool.starmap(column_transposition_break_worker,
+ helper_args, chunksize)
+ best = max(breaks, key=lambda k: k[1])
+ return math.trunc(len(message) / len(best[0][0])), best[1]
+scytale_break = scytale_break_mp
+
+
+def railfence_break(message, max_key_length=20,
+ fitness=Pletters, chunksize=500):
+ """Breaks a hill cipher using a matrix of given rank and letter frequencies
+
+
+ """
+
+ sanitised_message = sanitise(message)
+ results = starmap(worker, [(sanitised_message, i, fitness)
+ for i in range(2, max_key_length+1)])
+ return max(results, key=lambda k: k[1])
+
+
+def railfence_break(message, max_key_length=20,
+ fitness=Pbigrams, chunksize=500):
+ """Breaks a railfence cipher using a range of lengths and
+ n-gram frequency analysis
+
+ >>> railfence_break(railfence_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."), \
+ 7)) # doctest: +ELLIPSIS
+ (7, -709.46467226...)
+ >>> railfence_break(railfence_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."), \
+ 7), \
+ fitness=Ptrigrams) # doctest: +ELLIPSIS
+ (7, -997.0129085...)
+ """
+ def worker(message, height, fitness):
+ plaintext = railfence_decipher(message, height)
+ fit = fitness(plaintext)
+ return height, fit
+
+ sanitised_message = sanitise(message)
+ results = starmap(worker, [(sanitised_message, i, fitness)
+ for i in range(2, max_key_length+1)])
+ return max(results, key=lambda k: k[1])
+
+
+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
+
+
+def pocket_enigma_break_by_crib(message, wheel_spec, crib, crib_position):
+ """Break a pocket enigma using a crib (some plaintext that's expected to
+ be in a certain position). Returns a list of possible starting wheel
+ positions that could produce the crib.
+
+ >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'h', 0)
+ ['a', 'f', 'q']
+ >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'he', 0)
+ ['a']
+ >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'll', 2)
+ ['a']
+ >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'l', 2)
+ ['a']
+ >>> pocket_enigma_break_by_crib('kzpjlzmoga', 1, 'l', 3)
+ ['a', 'j', 'n']
+ >>> pocket_enigma_break_by_crib('aaaaa', 1, 'l', 3)
+ []
+ """
+ pe = PocketEnigma(wheel=wheel_spec)
+ possible_positions = []
+ for p in string.ascii_lowercase:
+ pe.set_position(p)
+ plaintext = pe.decipher(message)
+ if plaintext[crib_position:crib_position+len(crib)] == crib:
+ possible_positions += [p]
+ return possible_positions
+