X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=cipherbreak.py;h=b1af48c2e40d1ba60b948e75c758ab162f242233;hb=14ac787d794684b5aceacb307d687597e464e7b4;hp=6f4d2aed7d037e509df96970891a7b9e1808c088;hpb=159c157fee78d4370a0e603de450bd8e2def305b;p=cipher-tools.git diff --git a/cipherbreak.py b/cipherbreak.py index 6f4d2ae..b1af48c 100644 --- a/cipherbreak.py +++ b/cipherbreak.py @@ -416,6 +416,19 @@ def scytale_break_mp(message, max_key_length=20, 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 @@ -445,24 +458,43 @@ def railfence_break(message, max_key_length=20, 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, 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)]] + 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(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 + 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):