X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=cipherbreak.py;h=6e08f49b05f327e24481734b22d115f727048dd9;hb=9ab85aeac37f3ceada3d7d959a7f5ef8835638f5;hp=eae56cfc3c8560ae50f33c60b8b8d8a9e95f622f;hpb=f23ebe9e009e465a31bd0aac2a9d9cd82da642f8;p=cipher-training.git diff --git a/cipherbreak.py b/cipherbreak.py index eae56cf..6e08f49 100644 --- a/cipherbreak.py +++ b/cipherbreak.py @@ -184,6 +184,50 @@ def keyword_break_worker(message, keyword, wrap_alphabet, fitness): wrap_alphabet, fit, sanitise(plaintext)[:50])) return (keyword, wrap_alphabet), fit +def monoalphabetic_break_hillclimbing(message, max_iterations = 10000000, + fitness=Pletters): + ciphertext = unaccent(message).lower() + alphabet = list(string.ascii_lowercase) + random.shuffle(alphabet) + alphabet = ''.join(alphabet) + return monoalphabetic_break_hillclimbing_worker(ciphertext, alphabet, + max_iterations, fitness) + +def monoalphabetic_break_hillclimbing_mp(message, workers=10, + max_iterations = 10000000, fitness=Pletters, chunksize=1): + worker_args = [] + ciphertext = unaccent(message).lower() + for i in range(workers): + alphabet = list(string.ascii_lowercase) + random.shuffle(alphabet) + alphabet = ''.join(alphabet) + worker_args.append((ciphertext, alphabet, max_iterations, fitness)) + with Pool() as pool: + breaks = pool.starmap(monoalphabetic_break_hillclimbing_worker, + worker_args, chunksize) + return max(breaks, key=lambda k: k[1]) + +def monoalphabetic_break_hillclimbing_worker(message, alphabet, + max_iterations, fitness): + def swap(letters, i, j): + if i > j: + i, j = j, i + if i == j: + return letters + else: + return letters[:i] + letters[j] + letters[i+1:j] + letters[i] + letters[j+1:] + best_alphabet = alphabet + best_fitness = float('-inf') + for i in range(max_iterations): + alphabet = swap(alphabet, random.randrange(26), random.randrange(26)) + cipher_translation = ''.maketrans(string.ascii_lowercase, alphabet) + plaintext = message.translate(cipher_translation) + if fitness(plaintext) > best_fitness: + best_fitness = fitness(plaintext) + best_alphabet = alphabet + print(i, best_alphabet, best_fitness, plaintext) + return best_alphabet, best_fitness + def column_transposition_break_mp(message, translist=transpositions, fitness=Pbigrams, chunksize=500): @@ -229,51 +273,6 @@ def column_transposition_break_mp(message, translist=transpositions, return max(breaks, key=lambda k: k[1]) column_transposition_break = column_transposition_break_mp -def monoalphabetic_break_hillclimbing(message, max_iterations = 10000000, - fitness=Pletters): - ciphertext = unaccent(message).lower() - alphabet = list(string.ascii_lowercase) - random.shuffle(alphabet) - alphabet = ''.join(alphabet) - return monoalphabetic_break_hillclimbing_worker(ciphertext, alphabet, - max_iterations, fitness) - -def monoalphabetic_break_hillclimbing_mp(message, workers=100, - max_iterations = 10000000, fitness=Pletters, chunksize=1): - worker_args = [] - ciphertext = unaccent(message).lower() - for i in range(workers): - alphabet = list(string.ascii_lowercase) - random.shuffle(alphabet) - alphabet = ''.join(alphabet) - worker_args.append((ciphertext, alphabet, max_iterations, fitness)) - with Pool() as pool: - breaks = pool.starmap(monoalphabetic_break_hillclimbing_worker, - worker_args, chunksize) - return max(breaks, key=lambda k: k[1]) - -def monoalphabetic_break_hillclimbing_worker(message, alphabet, - max_iterations, fitness): - def swap(letters, i, j): - if i > j: - i, j = j, i - if i == j: - return letters - else: - return letters[:i] + letters[j] + letters[i+1:j] + letters[i] + letters[j+1:] - best_alphabet = alphabet - best_fitness = float('-inf') - for i in range(max_iterations): - alphabet = swap(alphabet, random.randrange(26), random.randrange(26)) - cipher_translation = ''.maketrans(string.ascii_lowercase, alphabet) - plaintext = message.translate(cipher_translation) - if fitness(plaintext) > best_fitness: - best_fitness = fitness(plaintext) - best_alphabet = alphabet - print(i, best_alphabet, best_fitness, plaintext) - return best_alphabet, best_fitness - - def column_transposition_break_worker(message, transposition, fillcolumnwise, emptycolumnwise, fitness): plaintext = column_transposition_decipher(message, transposition, @@ -402,6 +401,34 @@ def beaufort_frequency_break(message, max_key_length=20, fitness=Pletters): return max(results, key=lambda k: k[1]) +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 + + def plot_frequency_histogram(freqs, sort_key=None): x = range(len(freqs.keys())) y = [freqs[l] for l in sorted(freqs.keys(), key=sort_key)]