daec253af300f6e39e23288b9609f781f1c97322
4 ##################################
6 ##################################
7 # Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.
9 # 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).
11 # There's also the nice Enigma simulator for Android by [Franklin Heath](https://franklinheath.co.uk/2012/02/04/our-first-app-published-enigma-simulator/), available on the [Google Play store](https://play.google.com/store/apps/details?id=uk.co.franklinheath.enigmasim&hl=en_GB).
17 import multiprocessing
20 # Some convenience functions
24 def clean(text
): return cat(l
.lower() for l
in text
if l
in string
.ascii_letters
)
27 if letter
in string
.ascii_lowercase
:
28 return ord(letter
) - ord('a')
29 elif letter
in string
.ascii_uppercase
:
30 return ord(letter
) - ord('A')
34 def unpos(number
): return chr(number
% 26 + ord('a'))
37 wheel_i_spec
= 'ekmflgdqvzntowyhxuspaibrcj'
38 wheel_ii_spec
= 'ajdksiruxblhwtmcqgznpyfvoe'
39 wheel_iii_spec
= 'bdfhjlcprtxvznyeiwgakmusqo'
40 wheel_iv_spec
= 'esovpzjayquirhxlnftgkdcmwb'
41 wheel_v_spec
= 'vzbrgityupsdnhlxawmjqofeck'
42 wheel_vi_spec
= 'jpgvoumfyqbenhzrdkasxlictw'
43 wheel_vii_spec
= 'nzjhgrcxmyswboufaivlpekqdt'
44 wheel_viii_spec
= 'fkqhtlxocbjspdzramewniuygv'
45 beta_wheel_spec
= 'leyjvcnixwpbqmdrtakzgfuhos'
46 gamma_wheel_spec
= 'fsokanuerhmbtiycwlqpzxvgjd'
48 wheel_i_notches
= ['q']
49 wheel_ii_notches
= ['e']
50 wheel_iii_notches
= ['v']
51 wheel_iv_notches
= ['j']
52 wheel_v_notches
= ['z']
53 wheel_vi_notches
= ['z', 'm']
54 wheel_vii_notches
= ['z', 'm']
55 wheel_viii_notches
= ['z', 'm']
57 reflector_b_spec
= 'ay br cu dh eq fs gl ip jx kn mo tz vw'
58 reflector_c_spec
= 'af bv cp dj ei go hy kr lz mx nw tq su'
62 class LetterTransformer(object):
63 """A generic substitution cipher, that has different transforms in the
64 forward and backward directions. It requires that the transforms for all
67 def __init__(self
, specification
, raw_transform
=False):
69 transform
= specification
71 transform
= self
.parse_specification(specification
)
72 self
.validate_transform(transform
)
73 self
.make_transform_map(transform
)
75 def parse_specification(self
, specification
):
76 return list(zip(string
.ascii_lowercase
, clean(specification
)))
77 # return specification
79 def validate_transform(self
, transform
):
80 """A set of pairs, of from-to"""
81 if len(transform
) != 26:
82 raise ValueError("Transform specification has {} pairs, requires 26".
83 format(len(transform
)))
86 raise ValueError("Not all mappings in transform "
88 if len(set([p
[0] for p
in transform
])) != 26:
89 raise ValueError("Transform specification must list 26 origin letters")
90 if len(set([p
[1] for p
in transform
])) != 26:
91 raise ValueError("Transform specification must list 26 destination letters")
93 def make_empty_transform(self
):
94 self
.forward_map
= [0] * 26
95 self
.backward_map
= [0] * 26
97 def make_transform_map(self
, transform
):
98 self
.make_empty_transform()
100 self
.forward_map
[pos(p
[0])] = pos(p
[1])
101 self
.backward_map
[pos(p
[1])] = pos(p
[0])
102 return self
.forward_map
, self
.backward_map
104 def forward(self
, letter
):
105 if letter
in string
.ascii_lowercase
:
106 return unpos(self
.forward_map
[pos(letter
)])
110 def backward(self
, letter
):
111 if letter
in string
.ascii_lowercase
:
112 return unpos(self
.backward_map
[pos(letter
)])
117 class Plugboard(LetterTransformer
):
118 """A plugboard, a type of letter transformer where forward and backward
119 transforms are the same. If a letter isn't explicitly transformed, it is
122 def parse_specification(self
, specification
):
123 return [tuple(clean(p
)) for p
in specification
.split()]
125 def validate_transform(self
, transform
):
126 """A set of pairs, of from-to"""
129 raise ValueError("Not all mappings in transform"
132 def make_empty_transform(self
):
133 self
.forward_map
= list(range(26))
134 self
.backward_map
= list(range(26))
136 def make_transform_map(self
, transform
):
137 expanded_transform
= transform
+ [tuple(reversed(p
)) for p
in transform
]
138 return super(Plugboard
, self
).make_transform_map(expanded_transform
)
143 class Reflector(Plugboard
):
144 """A reflector is a plugboard that requires 13 transforms.
146 def validate_transform(self
, transform
):
147 if len(transform
) != 13:
148 raise ValueError("Reflector specification has {} pairs, requires 13".
149 format(len(transform
)))
150 if len(set([p
[0] for p
in transform
] +
151 [p
[1] for p
in transform
])) != 26:
152 raise ValueError("Reflector specification does not contain 26 letters")
154 super(Reflector
, self
).validate_transform(transform
)
155 except ValueError as v
:
156 raise ValueError("Not all mappings in reflector have two elements")
161 class SimpleWheel(LetterTransformer
):
162 """A wheel is a transform that rotates.
164 Looking from the right, letters go in sequence a-b-c clockwise around the
167 The position of the wheel is the number of spaces anticlockwise the wheel
170 Letter inputs and outputs are given relative to the frame holding the wheel,
171 so if the wheel is advanced three places, an input of 'p' will enter the
172 wheel on the position under the wheel's 'q' label.
174 def __init__(self
, transform
, position
='a', raw_transform
=False):
175 super(SimpleWheel
, self
).__init
__(transform
, raw_transform
)
176 self
.set_position(position
)
178 def __getattribute__(self
,name
):
179 if name
=='position_l':
180 return unpos(self
.position
)
182 return object.__getattribute
__(self
, name
)
184 def set_position(self
, position
):
185 if isinstance(position
, str):
186 # self.position = ord(position) - ord('a')
187 self
.position
= pos(position
)
189 self
.position
= position
191 def forward(self
, letter
):
192 if letter
in string
.ascii_lowercase
:
193 return unpos((self
.forward_map
[(pos(letter
) + self
.position
) % 26] - self
.position
))
197 def backward(self
, letter
):
198 if letter
in string
.ascii_lowercase
:
199 return unpos((self
.backward_map
[(pos(letter
) + self
.position
) % 26] - self
.position
))
204 self
.position
= (self
.position
+ 1) % 26
208 class Wheel(SimpleWheel
):
209 """A wheel with a movable ring.
211 The ring holds the letters and the notches that turn other wheels. The core
212 holds the wiring that does the transformation.
214 The ring position is how many steps the core is turned relative to the ring.
215 This is one-based, so a ring setting of 1 means the core and ring are
218 The position of the wheel is the position of the core (the transforms)
219 relative to the neutral position.
221 The position_l is the position of the ring, or what would be observed
222 by the user of the Enigma machine.
224 The notch_positions are the number of advances of this wheel before it will
225 advance the next wheel.
228 def __init__(self
, transform
, ring_notch_letters
, ring_setting
=1, position
='a', raw_transform
=False):
229 self
.ring_notch_letters
= ring_notch_letters
230 self
.ring_setting
= ring_setting
231 super(Wheel
, self
).__init
__(transform
, position
=position
, raw_transform
=raw_transform
)
232 self
.set_position(position
)
234 def __getattribute__(self
,name
):
235 if name
=='position_l':
236 return unpos(self
.position
+ self
.ring_setting
- 1)
238 return object.__getattribute
__(self
, name
)
240 def set_position(self
, position
):
241 if isinstance(position
, str):
242 self
.position
= (pos(position
) - self
.ring_setting
+ 1) % 26
244 self
.position
= (position
- self
.ring_setting
) % 26
245 # # self.notch_positions = [(pos(p) - pos(position)) % 26 for p in self.ring_notch_letters]
246 # self.notch_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26 for p in self.ring_notch_letters]
247 self
.notch_positions
= [(self
.position
+ self
.ring_setting
- 1 - pos(p
)) % 26 for p
in self
.ring_notch_letters
]
250 super(Wheel
, self
).advance()
251 self
.notch_positions
= [(p
+ 1) % 26 for p
in self
.notch_positions
]
255 class Enigma(object):
256 """An Enigma machine.
260 def __init__(self
, reflector_spec
,
261 left_wheel_spec
, left_wheel_notches
,
262 middle_wheel_spec
, middle_wheel_notches
,
263 right_wheel_spec
, right_wheel_notches
,
264 left_ring_setting
, middle_ring_setting
, right_ring_setting
,
266 self
.reflector
= Reflector(reflector_spec
)
267 self
.left_wheel
= Wheel(left_wheel_spec
, left_wheel_notches
, ring_setting
=left_ring_setting
)
268 self
.middle_wheel
= Wheel(middle_wheel_spec
, middle_wheel_notches
, ring_setting
=middle_ring_setting
)
269 self
.right_wheel
= Wheel(right_wheel_spec
, right_wheel_notches
, ring_setting
=right_ring_setting
)
270 self
.plugboard
= Plugboard(plugboard_setting
)
272 def __getattribute__(self
,name
):
273 if name
=='wheel_positions':
274 return self
.left_wheel
.position
, self
.middle_wheel
.position
, self
.right_wheel
.position
275 elif name
=='wheel_positions_l':
276 return self
.left_wheel
.position_l
, self
.middle_wheel
.position_l
, self
.right_wheel
.position_l
277 elif name
=='notch_positions':
278 return self
.left_wheel
.notch_positions
, self
.middle_wheel
.notch_positions
, self
.right_wheel
.notch_positions
280 return object.__getattribute
__(self
, name
)
282 def set_wheels(self
, left_wheel_position
, middle_wheel_position
, right_wheel_position
):
283 self
.left_wheel
.set_position(left_wheel_position
)
284 self
.middle_wheel
.set_position(middle_wheel_position
)
285 self
.right_wheel
.set_position(right_wheel_position
)
287 def lookup(self
, letter
):
288 a
= self
.plugboard
.forward(letter
)
289 b
= self
.right_wheel
.forward(a
)
290 c
= self
.middle_wheel
.forward(b
)
291 d
= self
.left_wheel
.forward(c
)
292 e
= self
.reflector
.forward(d
)
293 f
= self
.left_wheel
.backward(e
)
294 g
= self
.middle_wheel
.backward(f
)
295 h
= self
.right_wheel
.backward(g
)
296 i
= self
.plugboard
.backward(h
)
300 advance_middle
= False
302 if 0 in self
.right_wheel
.notch_positions
:
303 advance_middle
= True
304 if 0 in self
.middle_wheel
.notch_positions
:
306 advance_middle
= True
307 self
.right_wheel
.advance()
308 if advance_middle
: self
.middle_wheel
.advance()
309 if advance_left
: self
.left_wheel
.advance()
311 def encipher_letter(self
, letter
):
313 return self
.lookup(letter
)
315 def encipher(self
, message
):
317 for letter
in clean(message
):
318 enciphered
+= self
.encipher_letter(letter
)
324 # for i in range(26):
326 # print('enigma.advance()')
327 # print("assert(enigma.wheel_positions == {})".format(enigma.wheel_positions))
328 # print("assert(cat(enigma.wheel_positions_l) == '{}')".format(cat(enigma.wheel_positions_l)))
329 # print("assert(enigma.notch_positions == {})".format(enigma.notch_positions))
330 # print("assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))
334 if __name__
== "__main__":
336 # doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})