def test_swap_methods(self): plugboard = enigma.Swapper(n_positions=10) # element swap plugboard.set_element_swap(3, 7) self.assertEqual(plugboard.get_output(3), 7) self.assertEqual(plugboard.get_output(7), 3) self.assertEqual(plugboard.get_output(2), 2) # swap move plugboard.move_one_swap_side(7, 8) self.assertEqual(plugboard.get_output(3), 8) self.assertEqual(plugboard.get_output(8), 3) self.assertEqual(plugboard.get_output(7), 7) self.assertEqual(plugboard.get_output(2), 2) with self.assertRaises(ValueError): plugboard.move_one_swap_side(2, 3) plugboard.set_element_swap(2, 1) with self.assertRaises(ValueError): plugboard.move_one_swap_side(8, 2) # swap remove plugboard.unset_element_swap(3, 8) self.assertEqual(plugboard.get_output(7), 7) self.assertEqual(plugboard.get_output(8), 8)
def setup_enigma_and_msg(self, len_msg, n_rotors, n_plugs): self.charset = string.ascii_lowercase self.n_chars = len(self.charset) self.reflector = enigma.Swapper(n_positions=self.n_chars) self.reflector.assign_random_swaps(n_swaps=self.n_chars // 2, seed=3) rotor_seeds = list(range(n_rotors)) self.rotors = [ enigma.Rotor(n_positions=self.n_chars, seed=seed) for seed in rotor_seeds ] self.rotor_positions = n_rotors * [15] self.plugboard = enigma.Swapper(n_positions=self.n_chars) self.plugboard.assign_random_swaps(n_swaps=n_plugs, seed=41) message = ( "The Enigma machine is a cipher device developed and used in the early- to mid-20th century to protect" " commercial, diplomatic, and military communication. It was employed extensively by Nazi Germany during " "World War II, in all branches of the German military. The Germans believed, erroneously, that use of the" " Enigma machine enabled them to communicate securely and thus enjoy a huge advantage in World War II. " "The Enigma machine was considered to be so secure that even the most top-secret messages were enciphered" " on its electrical circuits. Enigma has an electromechanical rotor mechanism that scrambles the 26 letters " "of the alphabet. In typical use, one person enters text on the Enigma's keyboard and another person writes" " down which of 26 lights above the keyboard lights up at each key press. If plain text is entered, the " "lit-up letters are the encoded ciphertext. Entering ciphertext transforms it back into readable plaintext. " "The rotor mechanism changes the electrical connections between the keys and the lights with each keypress. " "The security of the system depends on a set of machine settings that were generally changed daily during the " "war, based on secret key lists distributed in advance, and on other settings that were changed for " "each message. The receiving station has to know and use the exact settings employed by the transmitting " "station to successfully decrypt a message." ) message = message.lower() self.message_full = "".join(c for c in message if c.islower()) encoder = enigma.Enigma( self.rotors, self.plugboard, self.reflector, charset=self.charset ) encoder.set_rotor_positions(self.rotor_positions) self.message = self.message_full[:len_msg] self.encrypted_message = encoder.encode_message(self.message) # reset all rotors so MC does not start with a good value encoder.set_rotor_positions(n_rotors * [0])
def test_plugboard_random_swaps(self): plugboard = enigma.Swapper(n_positions=10) in_1 = 5 out_1 = plugboard.get_output(in_1) self.assertEqual(in_1, out_1) plugboard.assign_random_swaps(n_swaps=5, seed=42) in_0 = 3 out_0 = plugboard.get_output(in_0) self.assertNotEqual(in_0, out_0) self.assertEqual(in_0, plugboard.get_output(out_0))
def test_encrypt_decrypt(self): plugboard = enigma.Swapper(n_positions=self.n_chars) plugboard.assign_random_swaps(n_swaps=10, seed=41) rotor_seeds = [21, 32, 34] rotors = [ enigma.Rotor(n_positions=self.n_chars, seed=seed) for seed in rotor_seeds ] reflector = enigma.Swapper(n_positions=self.n_chars) reflector.assign_random_swaps(n_swaps=self.n_chars // 2, seed=3) encoder = enigma.Enigma(rotors, plugboard, reflector, charset=self.charset) rotor_positions = [3, 4, 7] encoder.set_rotor_positions(rotor_positions) encoded_message = encoder.encode_message(self.test_message) self.assertNotEqual(encoded_message, self.test_message) encoder.set_rotor_positions(rotor_positions) decoded_message = encoder.encode_message(encoded_message) self.assertEqual(decoded_message, self.test_message)
import time import string import random import tqdm import enigma n_messages = 3000 chars_per_message = 256 charset = string.ascii_uppercase n_chars = len(charset) plugboard = enigma.Swapper(n_positions=n_chars, n_swaps=10, seed=41) rotor_seeds = [21, 32, 34] rotors = [enigma.Rotor(n_positions=n_chars, seed=seed) for seed in rotor_seeds] reflector = enigma.Swapper(n_positions=n_chars, n_swaps=n_chars // 2, seed=3) encoder = enigma.Enigma(rotors, plugboard, reflector, charset=charset) rotor_positions = [3, 4, 7] messages = [ "".join(random.choices(charset, k=chars_per_message)) for _ in range(n_messages) ] tick = time.time() for message in tqdm.tqdm(messages): encoder.set_rotor_positions(rotor_positions) encoded_message = encoder.encode_message(message) tock = time.time() avg_time = (tock - tick) / n_messages
def decode_message_successive_best( encrypted_message, rotors: list, n_plugs, reflector: enigma.Swapper, scorer: TextScorerBase, charset=string.ascii_lowercase, disable_tqdm=False, ): n_chars = rotors[0].n_positions # test encoder knows the machine decoder_plugboard = enigma.Swapper(n_positions=n_chars) decoder_enigma = enigma.Enigma( copy.deepcopy(rotors), decoder_plugboard, copy.deepcopy(reflector), charset=charset, ) # go through all positions and get the score of the output text highscore = -np.inf best_pos = decoder_enigma.get_rotor_positions() # TODO Parallel? positions = iter(MultiindexIiterator(len(rotors), n_chars)) for pos in tqdm.tqdm(positions, disable=disable_tqdm): decoder_enigma.set_rotor_positions(pos) decoder_try = decoder_enigma.encode_message(encrypted_message) score = scorer.score_text(decoder_try) if score > highscore: highscore = score best_pos = pos # decode the plugboard # we have 10 plugs to distribute available_plug_positions = list(range(n_chars)) for i in tqdm.tqdm(range(n_plugs), disable=disable_tqdm): highscore = -np.inf best_swap = (0, 1) # go through all positions for the plug and get their score # TODO loop parallel? for first in available_plug_positions: for second in available_plug_positions: # we have to set a plug, self-connections are not allowed if first == second: continue decoder_plugboard.set_element_swap(first, second) decoder_enigma.set_rotor_positions(best_pos) decoder_try = decoder_enigma.encode_message(encrypted_message) score = scorer.score_text(decoder_try) if score > highscore: highscore = score best_swap = (first, second) decoder_plugboard.unset_element_swap(first, second) # use the best swap for further decrypting decoder_plugboard.set_element_swap(best_swap[0], best_swap[1]) available_plug_positions.remove(best_swap[0]) available_plug_positions.remove(best_swap[1]) decoder_enigma.set_rotor_positions(best_pos) decoded_msg = decoder_enigma.encode_message(encrypted_message) return decoded_msg, best_pos, decoder_plugboard
def decode_message_MC( encrypted_message, rotors: list, n_plugs: int, reflector: enigma.Swapper, scorer: TextScorerBase, score_scale: float = 1, n_attempts_per_block=1000, max_n_blocks=100, charset=string.ascii_lowercase, ): n_chars = rotors[0].n_positions # test encoder knows the machine decoder_plugboard = enigma.Swapper(n_positions=n_chars) decoder_plugboard.assign_random_swaps(n_swaps=n_plugs) decoder_enigma = enigma.Enigma( copy.deepcopy(rotors), decoder_plugboard, copy.deepcopy(reflector), charset=charset, ) last_rotor_positions = decoder_enigma.get_rotor_positions() last_dec_msg = decoder_enigma.encode_message(encrypted_message) last_score = scorer.score_text(last_dec_msg) rng = np.random.default_rng(42) last_block_avg_score = -np.inf for i in range(max_n_blocks): block_scores = list() block_accepted_rot = 0 block_accepted_plug = 0 for _ in range(n_attempts_per_block): # rotor move prop_rot_pos = _propose_rot_move(last_rotor_positions, n_chars, rng) decoder_enigma.set_rotor_positions(prop_rot_pos) accept, new_score = _assess_move(decoder_enigma, encrypted_message, scorer, last_score, score_scale, rng) if accept: block_accepted_rot += 1 last_score = new_score last_rotor_positions = prop_rot_pos if n_plugs > 0: # plugboard move prop_plug_move = _propose_plug_move(decoder_plugboard, rng) decoder_plugboard.move_one_swap_side(prop_plug_move[0], prop_plug_move[1]) accept, new_score = _assess_move( decoder_enigma, encrypted_message, scorer, last_score, score_scale, rng, ) if accept: last_score = new_score block_accepted_plug += 1 else: # undo the move decoder_plugboard.move_one_swap_side( prop_plug_move[1], prop_plug_move[0]) block_scores.append(last_score) block_avg_score = np.mean(block_scores) if (abs( (block_avg_score - last_block_avg_score) / last_block_avg_score) < 0.0001): break last_block_avg_score = block_avg_score # print(f'avg score {last_block_avg_score}') # print(f'acceptance rate rot {block_accepted_rot / n_attempts_per_block}') # print(f'acceptance rate plug {block_accepted_plug / n_attempts_per_block}') # use the final settings to return decoded_msg = decoder_enigma.encode_message(encrypted_message) return decoded_msg, last_rotor_positions, decoder_plugboard