X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=enigma.py;h=a0056e4aae61d34725d63ee82846d29f455859f2;hb=f817fcd4b8d45e4c95d71853278e28fb2d5c6f54;hp=1150763d35eabc8be0e4fa5cf1c4f5cc3ed26b6f;hpb=027a37ff0a8b8e5dd28c8f6ce3819315d1b7a284;p=cipher-tools.git diff --git a/enigma.py b/enigma.py index 1150763..a0056e4 100644 --- a/enigma.py +++ b/enigma.py @@ -1,7 +1,9 @@ # coding: utf-8 +################################## # # Enigma machine +################################## # Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page. # # Example Enigma machines from [Louise Dale](http://enigma.louisedade.co.uk/enigma.html) (full simulation) and [EnigmaCo](http://enigmaco.de/enigma/enigma.html) (good animation of the wheels, but no ring settings). @@ -12,6 +14,8 @@ import string import collections +import multiprocessing +import itertools # Some convenience functions @@ -59,24 +63,6 @@ class LetterTransformer(object): """A generic substitution cipher, that has different transforms in the forward and backward directions. It requires that the transforms for all letters by provided. - - >>> lt = LetterTransformer([('z', 'a')] + [(l, string.ascii_lowercase[i+1]) \ - for i, l in enumerate(string.ascii_lowercase[:-1])], \ - raw_transform = True) - >>> lt.forward_map - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0] - >>> lt.backward_map - [25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24] - - >>> lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase))) - >>> lt.forward_map - [25, 24, 23, 22, 2, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] - >>> lt.backward_map - [5, 6, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 3, 2, 1, 0] - >>> cat(lt.forward(l) for l in string.ascii_lowercase) - 'zyxwcabdefghijklmnopqrstuv' - >>> cat(lt.backward(l) for l in string.ascii_lowercase) - 'fgehijklmnopqrstuvwxyzdcba' """ def __init__(self, specification, raw_transform=False): if raw_transform: @@ -132,16 +118,6 @@ class Plugboard(LetterTransformer): """A plugboard, a type of letter transformer where forward and backward transforms are the same. If a letter isn't explicitly transformed, it is kept as it is. - - >>> pb = Plugboard('ua pf rq so ni ey bg hl tx zj'.upper()) - >>> pb.forward_map - [20, 6, 2, 3, 24, 15, 1, 11, 13, 25, 10, 7, 12, 8, 18, 5, 17, 16, 14, 23, 0, 21, 22, 19, 4, 9] - >>> pb.forward_map == pb.backward_map - True - >>> cat(pb.forward(l) for l in string.ascii_lowercase) - 'ugcdypblnzkhmisfrqoxavwtej' - >>> cat(pb.backward(l) for l in string.ascii_lowercase) - 'ugcdypblnzkhmisfrqoxavwtej' """ def parse_specification(self, specification): return [tuple(clean(p)) for p in specification.split()] @@ -166,16 +142,6 @@ class Plugboard(LetterTransformer): class Reflector(Plugboard): """A reflector is a plugboard that requires 13 transforms. - - >>> reflector_b = Reflector(reflector_b_spec) - >>> reflector_b.forward_map == reflector_b.backward_map - True - >>> reflector_b.forward_map - [24, 17, 20, 7, 16, 18, 11, 3, 15, 23, 13, 6, 14, 10, 12, 8, 4, 1, 5, 25, 2, 22, 21, 9, 0, 19] - >>> cat(reflector_b.forward(l) for l in string.ascii_lowercase) - 'yruhqsldpxngokmiebfzcwvjat' - >>> cat(reflector_b.backward(l) for l in string.ascii_lowercase) - 'yruhqsldpxngokmiebfzcwvjat' """ def validate_transform(self, transform): if len(transform) != 13: @@ -204,52 +170,6 @@ class SimpleWheel(LetterTransformer): Letter inputs and outputs are given relative to the frame holding the wheel, so if the wheel is advanced three places, an input of 'p' will enter the wheel on the position under the wheel's 'q' label. - - >>> rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower())) - >>> wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True) - >>> cat(wheel_1.forward(l) for l in string.ascii_lowercase) - 'ekmflgdqvzntowyhxuspaibrcj' - >>> cat(wheel_1.backward(l) for l in string.ascii_lowercase) - 'uwygadfpvzbeckmthxslrinqoj' - - - >>> wheel_2 = SimpleWheel(wheel_ii_spec) - >>> cat(wheel_2.forward(l) for l in string.ascii_lowercase) - 'ajdksiruxblhwtmcqgznpyfvoe' - >>> cat(wheel_2.backward(l) for l in string.ascii_lowercase) - 'ajpczwrlfbdkotyuqgenhxmivs' - - >>> wheel_3 = SimpleWheel(wheel_iii_spec) - >>> wheel_3.set_position('a') - >>> wheel_3.advance() - >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase) - 'cegikboqswuymxdhvfzjltrpna' - >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase) - 'zfaobrcpdteumygxhwivkqjnls' - >>> wheel_3.position - 1 - >>> wheel_3.position_l - 'b' - - >>> for _ in range(24): wheel_3.advance() - >>> wheel_3.position - 25 - >>> wheel_3.position_l - 'z' - >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase) - 'pcegikmdqsuywaozfjxhblnvtr' - >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase) - 'nubhcqdterfvgwoaizjykxmslp' - - >>> wheel_3.advance() - >>> wheel_3.position - 0 - >>> wheel_3.position_l - 'a' - >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase) - 'bdfhjlcprtxvznyeiwgakmusqo' - >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase) - 'tagbpcsdqeufvnzhyixjwlrkom' """ def __init__(self, transform, position='a', raw_transform=False): super(SimpleWheel, self).__init__(transform, raw_transform) @@ -262,7 +182,11 @@ class SimpleWheel(LetterTransformer): return object.__getattribute__(self, name) def set_position(self, position): - self.position = ord(position) - ord('a') + if isinstance(position, str): + # self.position = ord(position) - ord('a') + self.position = pos(position) + else: + self.position = position def forward(self, letter): if letter in string.ascii_lowercase: @@ -278,7 +202,6 @@ class SimpleWheel(LetterTransformer): def advance(self): self.position = (self.position + 1) % 26 - # return self.position @@ -301,119 +224,6 @@ class Wheel(SimpleWheel): The peg_positions are the number of advances of this wheel before it will advance the next wheel. - >>> wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1) - >>> wheel_3.position - 1 - >>> wheel_3.peg_positions - [20] - >>> wheel_3.position_l - 'b' - >>> wheel_3.advance() - >>> wheel_3.position - 2 - >>> wheel_3.peg_positions - [19] - >>> wheel_3.position_l - 'c' - - >>> wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3) - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'xkqhwpvngzrcfoiaselbtymjdu' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'ptlyrmidoxbswhnfckquzgeavj' - >>> wheel_6.position - 25 - >>> 11 in wheel_6.peg_positions - True - >>> 24 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'b' - - >>> wheel_6.advance() - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'jpgvoumfyqbenhzrdkasxlictw' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'skxqlhcnwarvgmebjptyfdzuio' - >>> wheel_6.position - 0 - >>> 10 in wheel_6.peg_positions - True - >>> 23 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'c' - - >>> for _ in range(22): wheel_6.advance() - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'mgxantkzsyqjcufirldvhoewbp' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'dymswobuplgraevzkqifntxcjh' - >>> wheel_6.position - 22 - >>> 1 in wheel_6.peg_positions - True - >>> 14 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'y' - - >>> wheel_6.advance() - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'fwzmsjyrxpibtehqkcugndvaol' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'xlrvnatokfqzduyjphemswbigc' - >>> wheel_6.position - 23 - >>> 0 in wheel_6.peg_positions - True - >>> 13 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'z' - - >>> wheel_6.advance() - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'vylrixqwohasdgpjbtfmcuznke' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'kqumzsnjepyctxiogdlrvahfbw' - >>> wheel_6.position - 24 - >>> 25 in wheel_6.peg_positions - True - >>> 12 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'a' - - >>> wheel_6.advance() - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'xkqhwpvngzrcfoiaselbtymjdu' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'ptlyrmidoxbswhnfckquzgeavj' - >>> wheel_6.position - 25 - >>> 24 in wheel_6.peg_positions - True - >>> 11 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'b' - - >>> wheel_6.advance() - >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase) - 'jpgvoumfyqbenhzrdkasxlictw' - >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase) - 'skxqlhcnwarvgmebjptyfdzuio' - >>> wheel_6.position - 0 - >>> 23 in wheel_6.peg_positions - True - >>> 10 in wheel_6.peg_positions - True - >>> wheel_6.position_l - 'c' - """ def __init__(self, transform, ring_peg_letters, ring_setting=1, position='a', raw_transform=False): self.ring_peg_letters = ring_peg_letters @@ -428,505 +238,22 @@ class Wheel(SimpleWheel): return object.__getattribute__(self, name) def set_position(self, position): - self.position = (pos(position) - self.ring_setting + 1) % 26 - # self.position_l = position - self.peg_positions = [(pos(p) - pos(position)) % 26 for p in self.ring_peg_letters] + if isinstance(position, str): + self.position = (pos(position) - self.ring_setting + 1) % 26 + else: + self.position = (position - self.ring_setting) % 26 + # self.peg_positions = [(pos(p) - pos(position)) % 26 for p in self.ring_peg_letters] + self.peg_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26 for p in self.ring_peg_letters] def advance(self): super(Wheel, self).advance() self.peg_positions = [(p - 1) % 26 for p in self.peg_positions] - # self.position_l = unpos(self.position + self.ring_setting - 1) - # return self.position - - class Enigma(object): """An Enigma machine. - >>> enigma = Enigma(reflector_b_spec, \ - wheel_i_spec, wheel_i_pegs, \ - wheel_ii_spec, wheel_ii_pegs, \ - wheel_iii_spec, wheel_iii_pegs, \ - 1, 1, 1, \ - '') - >>> enigma.set_wheels('a', 'a', 't') - >>> enigma.wheel_positions - (0, 0, 19) - >>> cat(enigma.wheel_positions_l) - 'aat' - >>> enigma.peg_positions - ([16], [4], [2]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'puvioztjdhxmlyeawsrgbcqknf' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 0, 20) - >>> cat(enigma.wheel_positions_l) - 'aau' - >>> enigma.peg_positions - ([16], [4], [1]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'baigpldqcowfyzjehvtsxrkumn' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 0, 21) - >>> cat(enigma.wheel_positions_l) - 'aav' - >>> enigma.peg_positions - ([16], [4], [0]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'mnvfydiwgzsoablrxpkutchqej' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 1, 22) - >>> cat(enigma.wheel_positions_l) - 'abw' - >>> enigma.peg_positions - ([16], [3], [25]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'ulfopcykswhbzvderqixanjtgm' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 1, 23) - >>> cat(enigma.wheel_positions_l) - 'abx' - >>> enigma.peg_positions - ([16], [3], [24]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'qmwftdyovursbzhxaklejicpgn' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 1, 24) - >>> cat(enigma.wheel_positions_l) - 'aby' - >>> enigma.peg_positions - ([16], [3], [23]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'oljmzxrvucybdqasngpwihtfke' - - - - - >>> enigma.set_wheels('a', 'd', 't') - >>> enigma.wheel_positions - (0, 3, 19) - >>> cat(enigma.wheel_positions_l) - 'adt' - >>> enigma.peg_positions - ([16], [1], [2]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'zcbpqxwsjiuonmldethrkygfva' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 3, 20) - >>> cat(enigma.wheel_positions_l) - 'adu' - >>> enigma.peg_positions - ([16], [1], [1]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'ehprawjbngotxikcsdqlzyfmvu' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 3, 21) - >>> cat(enigma.wheel_positions_l) - 'adv' - >>> enigma.peg_positions - ([16], [1], [0]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'eqzxarpihmnvjkwgbfuyslodtc' - - >>> enigma.advance() - >>> enigma.wheel_positions - (0, 4, 22) - >>> cat(enigma.wheel_positions_l) - 'aew' - >>> enigma.peg_positions - ([16], [0], [25]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'qedcbtpluzmhkongavwfirsyxj' - - >>> enigma.advance() - >>> enigma.wheel_positions - (1, 5, 23) - >>> cat(enigma.wheel_positions_l) - 'bfx' - >>> enigma.peg_positions - ([15], [25], [24]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'iwuedhsfazqxytvrkpgncoblmj' - - >>> enigma.advance() - >>> enigma.wheel_positions - (1, 5, 24) - >>> cat(enigma.wheel_positions_l) - 'bfy' - >>> enigma.peg_positions - ([15], [25], [23]) - >>> cat(enigma.lookup(l) for l in string.ascii_lowercase) - 'baknstqzrmcxjdvygiefwoulph' - - - >>> enigma.set_wheels('a', 'a', 'a') - >>> ct = enigma.encipher('testmessage') - >>> ct - 'olpfhnvflyn' - - >>> enigma.set_wheels('a', 'd', 't') - >>> ct = enigma.encipher('testmessage') - >>> ct - 'lawnjgpwjik' - - - >>> enigma.set_wheels('b', 'd', 'q') - >>> ct = enigma.encipher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') - >>> ct - 'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr' - >>> enigma.left_wheel.position_l - 'c' - >>> enigma.middle_wheel.position_l - 'h' - >>> enigma.right_wheel.position_l - 'a' - - # Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm - # Enigma simulation settings are - # http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJEU;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX - >>> enigma31 = Enigma(reflector_b_spec, \ - wheel_i_spec, wheel_i_pegs, \ - wheel_v_spec, wheel_v_pegs, \ - wheel_iii_spec, wheel_iii_pegs, \ - 6, 20, 24, \ - 'ua pf rq so ni ey bg hl tx zj') - - >>> enigma31.set_wheels('j', 'e', 'u') - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (4, 11, 24) - >>> cat(enigma31.wheel_positions_l) - 'jev' - >>> enigma31.peg_positions - ([7], [21], [0]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'mvqjlyowkdieasgzcunxrbhtfp' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (4, 12, 25) - >>> cat(enigma31.wheel_positions_l) - 'jfw' - >>> enigma31.peg_positions - ([7], [20], [25]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'sjolzuyvrbwdpxcmtiaqfhknge' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (4, 12, 0) - >>> cat(enigma31.wheel_positions_l) - 'jfx' - >>> enigma31.peg_positions - ([7], [20], [24]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'qrxedkoywufmlvgsabpzjnicht' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (4, 12, 1) - >>> cat(enigma31.wheel_positions_l) - 'jfy' - >>> enigma31.peg_positions - ([7], [20], [23]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'hpsukliagqefwvtbjxcodnmrzy' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (4, 12, 2) - >>> cat(enigma31.wheel_positions_l) - 'jfz' - >>> enigma31.peg_positions - ([7], [20], [22]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'zevnbpyqowrtxdifhkulscjmga' - - - >>> enigma31.set_wheels('i', 'd', 'z') - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 3) - >>> cat(enigma31.wheel_positions_l) - 'ida' - >>> enigma31.peg_positions - ([8], [22], [21]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'ikhpqrvcambzjondefwyxgsutl' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 4) - >>> cat(enigma31.wheel_positions_l) - 'idb' - >>> enigma31.peg_positions - ([8], [22], [20]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'cdabskhgzwfmlqvunyexpojtri' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 5) - >>> cat(enigma31.wheel_positions_l) - 'idc' - >>> enigma31.peg_positions - ([8], [22], [19]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'pcbwiqhgemyvjsuaftnroldzkx' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 6) - >>> cat(enigma31.wheel_positions_l) - 'idd' - >>> enigma31.peg_positions - ([8], [22], [18]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'xcbfvdnouptmlghjzwykierasq' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 7) - >>> cat(enigma31.wheel_positions_l) - 'ide' - >>> enigma31.peg_positions - ([8], [22], [17]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'xfvglbdynuseriwqpmkzjcoaht' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 8) - >>> cat(enigma31.wheel_positions_l) - 'idf' - >>> enigma31.peg_positions - ([8], [22], [16]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'tfpqlbouynsewjgcdxkahzmriv' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 9) - >>> cat(enigma31.wheel_positions_l) - 'idg' - >>> enigma31.peg_positions - ([8], [22], [15]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'cjaunvlwtbygzexrspqidfhokm' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 10) - >>> cat(enigma31.wheel_positions_l) - 'idh' - >>> enigma31.peg_positions - ([8], [22], [14]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'yltxkrqvowebzpingfucshjdam' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 11) - >>> cat(enigma31.wheel_positions_l) - 'idi' - >>> enigma31.peg_positions - ([8], [22], [13]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'myktluzrnxceaiqsohpdfwvjbg' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 12) - >>> cat(enigma31.wheel_positions_l) - 'idj' - >>> enigma31.peg_positions - ([8], [22], [12]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'pynjrmiugdqxfcvakewzhoslbt' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 13) - >>> cat(enigma31.wheel_positions_l) - 'idk' - >>> enigma31.peg_positions - ([8], [22], [11]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'mwvedyplnoxhaijgrqtszcbkfu' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 14) - >>> cat(enigma31.wheel_positions_l) - 'idl' - >>> enigma31.peg_positions - ([8], [22], [10]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'qcbrfeutvoxpnmjladzhgiykws' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 15) - >>> cat(enigma31.wheel_positions_l) - 'idm' - >>> enigma31.peg_positions - ([8], [22], [9]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'dnoahryetsmukbcvwfjilpqzgx' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 16) - >>> cat(enigma31.wheel_positions_l) - 'idn' - >>> enigma31.peg_positions - ([8], [22], [8]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'nidcfehgbqsovalyjzkxwmutpr' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 17) - >>> cat(enigma31.wheel_positions_l) - 'ido' - >>> enigma31.peg_positions - ([8], [22], [7]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'joifxdulcarhzpbntkwqgysevm' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 18) - >>> cat(enigma31.wheel_positions_l) - 'idp' - >>> enigma31.peg_positions - ([8], [22], [6]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'ptnlsxvozmwdjchayuebrgkfqi' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 19) - >>> cat(enigma31.wheel_positions_l) - 'idq' - >>> enigma31.peg_positions - ([8], [22], [5]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'slwopzqnmxybihdeguavrtcjkf' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 20) - >>> cat(enigma31.wheel_positions_l) - 'idr' - >>> enigma31.peg_positions - ([8], [22], [4]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'hcbedwlamzogixkytsrqvufnpj' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 21) - >>> cat(enigma31.wheel_positions_l) - 'ids' - >>> enigma31.peg_positions - ([8], [22], [3]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'odxbjwzrmelkisavuhnyqpfctg' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 22) - >>> cat(enigma31.wheel_positions_l) - 'idt' - >>> enigma31.peg_positions - ([8], [22], [2]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'udgbfeclrwnhxksvtioqapjmzy' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 23) - >>> cat(enigma31.wheel_positions_l) - 'idu' - >>> enigma31.peg_positions - ([8], [22], [1]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'nrdczqxmowvshaiufblypkjgte' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 10, 24) - >>> cat(enigma31.wheel_positions_l) - 'idv' - >>> enigma31.peg_positions - ([8], [22], [0]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'hkifjdoacebqtzgulyvmpsxwrn' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 11, 25) - >>> cat(enigma31.wheel_positions_l) - 'iew' - >>> enigma31.peg_positions - ([8], [21], [25]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'yptzuhofqvnmlkgbixwcejsrad' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 11, 0) - >>> cat(enigma31.wheel_positions_l) - 'iex' - >>> enigma31.peg_positions - ([8], [21], [24]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'vkdcwhqfjibzsptngumoraeyxl' - - >>> enigma31.advance() - >>> enigma31.wheel_positions - (3, 11, 1) - >>> cat(enigma31.wheel_positions_l) - 'iey' - >>> enigma31.peg_positions - ([8], [21], [23]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'wenpbqrouxlkychdfgzvitajms' - - - >>> enigma31.set_wheels('i', 'd', 'z') - >>> enigma31.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure') - 'gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz' - >>> enigma31.wheel_positions - (3, 12, 6) - >>> cat(enigma31.wheel_positions_l) - 'ifd' - >>> enigma31.peg_positions - ([8], [20], [18]) - >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase) - 'urygzpdmxtwshqvfnbljaokice' - - >>> enigma31.set_wheels('i', 'd', 'z') - >>> enigma31.decipher('gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz') - 'verylongtestmessagewithanextrabitofmessageforgoodmeasure' + """ def __init__(self, reflector_spec, left_wheel_spec, left_wheel_pegs, @@ -1002,6 +329,189 @@ class Enigma(object): # print() +################################## +# # Bombe +################################## +# +# Good explanation of [how the bombe worked](http://www.ellsbury.com/enigmabombe.htm) by Graham Ellsbury +# + +Signal = collections.namedtuple('Signal', ['bank', 'wire']) +Connection = collections.namedtuple('Connection', ['banks', 'scrambler']) +MenuItem = collections.namedtuple('MenuIem', ['before', 'after', 'number']) + + +class Scrambler(object): + def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, + wheel1_pos='a', wheel2_pos='a', wheel3_pos='a'): + self.wheel1 = SimpleWheel(wheel1_spec, position=wheel1_pos) + self.wheel2 = SimpleWheel(wheel2_spec, position=wheel2_pos) + self.wheel3 = SimpleWheel(wheel3_spec, position=wheel3_pos) + self.reflector = Reflector(reflector_spec) + + def __getattribute__(self, name): + if name=='wheel_positions': + return self.wheel1.position, self.wheel2.position, self.wheel3.position + elif name=='wheel_positions_l': + return self.wheel1.position_l, self.wheel2.position_l, self.wheel3.position_l + else: + return object.__getattribute__(self, name) + + def advance(self, wheel1=False, wheel2=False, wheel3=True): + if wheel1: self.wheel1.advance() + if wheel2: self.wheel2.advance() + if wheel3: self.wheel3.advance() + + def lookup(self, letter): + a = self.wheel3.forward(letter) + b = self.wheel2.forward(a) + c = self.wheel1.forward(b) + d = self.reflector.forward(c) + e = self.wheel1.backward(d) + f = self.wheel2.backward(e) + g = self.wheel3.backward(f) + return g + + def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos): + self.wheel1.set_position(wheel1_pos) + self.wheel2.set_position(wheel2_pos) + self.wheel3.set_position(wheel3_pos) + + +class Bombe(object): + def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, + menu=None, start_signal=None, use_diagonal_board=True, + verify_plugboard=True): + self.connections = [] + self.wheel1_spec = wheel1_spec + self.wheel2_spec = wheel2_spec + self.wheel3_spec = wheel3_spec + self.reflector_spec = reflector_spec + if menu: + self.read_menu(menu) + if start_signal: + self.test_start = start_signal + self.use_diagonal_board = use_diagonal_board + self.verify_plugboard = verify_plugboard + + def __getattribute__(self, name): + if name=='wheel_positions': + return self.connections[0].scrambler.wheel_positions + elif name=='wheel_positions_l': + return self.connections[0].scrambler.wheel_positions_l + else: + return object.__getattribute__(self, name) + + def __call__(self, start_positions): + return start_positions, self.test(initial_signal=self.test_start, + start_positions=start_positions, + use_diagonal_board=self.use_diagonal_board, + verify_plugboard=self.verify_plugboard) + + def add_connection(self, bank_before, bank_after, scrambler): + self.connections += [Connection([bank_before, bank_after], scrambler)] + + def read_menu(self, menu): + for item in menu: + scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec, + self.reflector_spec, + wheel3_pos=unpos(item.number - 1)) + self.add_connection(item.before, item.after, scrambler) + most_common_letter = (collections.Counter(m.before for m in menu) + \ + collections.Counter(m.after for m in menu)).most_common(1)[0][0] + self.test_start = Signal(most_common_letter, most_common_letter) + + def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos): + for i, c in enumerate(self.connections): + c.scrambler.set_positions(wheel1_pos, wheel2_pos, unpos(pos(wheel3_pos) + i)) + + def test(self, initial_signal=None, start_positions=None, use_diagonal_board=True, + verify_plugboard=True): + self.banks = {label: + dict(zip(string.ascii_lowercase, [False]*len(string.ascii_lowercase))) + for label in string.ascii_lowercase} + if start_positions: + self.set_positions(*start_positions) + if not initial_signal: + initial_signal = self.test_start + self.pending = [initial_signal] + self.propagate(use_diagonal_board) + live_wire_count = len([self.banks[self.test_start.bank][w] + for w in self.banks[self.test_start.bank] + if self.banks[self.test_start.bank][w]]) + if live_wire_count < 26: + if verify_plugboard: + possibles = self.possible_plugboards() + return all(s0.isdisjoint(s1) for s0 in possibles for s1 in possibles if s0 != s1) + else: + return True + else: + return False + + def propagate(self, use_diagonal_board): + while self.pending: + current = self.pending[0] + # print("processing", current) + self.pending = self.pending[1:] + if not self.banks[current.bank][current.wire]: + self.banks[current.bank][current.wire] = True + if use_diagonal_board: + self.pending += [Signal(current.wire, current.bank)] + for c in self.connections: + if current.bank in c.banks: + other_bank = [b for b in c.banks if b != current.bank][0] + other_wire = c.scrambler.lookup(current.wire) + # print(" adding", other_bank, other_wire, "because", c.banks) + self.pending += [Signal(other_bank, other_wire)] + + def run(self, run_start=None, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a', use_diagonal_board=True): + if not run_start: + run_start = self.test_start + self.solutions = [] + self.set_positions(wheel1_pos, wheel2_pos, wheel3_pos) + for run_index in range(26*26*26): + if self.test(initial_signal=run_start, use_diagonal_board=use_diagonal_board): + self.solutions += [self.connections[0].scrambler.wheel_positions_l] + advance3 = True + advance2 = False + advance1 = False + if (run_index + 1) % 26 == 0: advance2 = True + if (run_index + 1) % (26*26) == 0: advance1 = True + for c in self.connections: + c.scrambler.advance(advance1, advance2, advance3) + return self.solutions + + def possible_plugboards(self): + possibles = set() + for b in self.banks: + active = [w for w in self.banks[b] if self.banks[b][w]] + inactive = [w for w in self.banks[b] if not self.banks[b][w]] + if len(active) == 1: + possibles = possibles.union({frozenset((b, active[0]))}) + if len(inactive) == 1: + possibles = possibles.union({frozenset((b, inactive[0]))}) + return possibles + + +def make_menu(plaintext, ciphertext): + return [MenuItem(p, c, i+1) + for i, (p, c) in enumerate(zip(plaintext, ciphertext))] + + +def run_multi_bombe(wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu, + start_signal=None, use_diagonal_board=True, + verify_plugboard=True): + allwheels = itertools.product(string.ascii_lowercase, repeat=3) + + with multiprocessing.Pool() as pool: + res = pool.map(Bombe(wheel1_spec, wheel2_spec, wheel3_spec, + reflector_spec, menu=menu, start_signal=start_signal, + use_diagonal_board=use_diagonal_board, + verify_plugboard=verify_plugboard), + allwheels) + return [r[0] for r in res if r[1]] + + if __name__ == "__main__": import doctest # doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})