Enigma_machine

查看源图像

1 加密

1.1 原理

img

想要实施加密,一个明文字母需要通过键盘输入。字母首先通过插头,然后依次通过三个转子中的每一个,在通过反射器,返回来再通过三个转子中的每一个,再返回插头,最终的字母呈现在显示器上。每个转子和反射器的实质是26个字母的置换。

随着每次一个字母被输入,最右边的转子会前进一个位置,其他转子会以类似里程表的方式前进。反射器可视为固定转子,因为它做字母替换,但自身并不轮转。所以最终效果是随着每个字母的输入,置换会发生改变。

1.2 Enigma的密钥空间

组成密钥可变化的设置是:

  1. 转子的选择。
  2. 对于最右边的两个转子,其每个对应的可移动环的位置。其决定何时发生类似里程表计数的效果。
  3. 每个转子的初始位置。
  4. 插头中电缆数量和插入状态。
  5. 反射器的选取。

转子:

可移动环:

插头:是插头中插入条电缆的各种不同方式的数目。则有:

Enigma机允许同时插入六根电线,交换六对字母,则有:

反射器:反射器相当于拥有13条电缆的插头,

因此,Enigma的密钥空间为

1.3 转子

1.3.1 转子的回转机制

需要注意的是,在转子旋转过程中,只有转子自身在轮转,而转子边缘的电器触电不动。我们通过一个例子可以直观理解这个过程。 在置换[C,D,B,A]中,相应的偏移量如下:AC,两个位置偏移量;BD,两个位置偏移量;CB,三个位置偏移量(相对于转子);DA,一个位置偏移量。故对置换[C,D,B,A]而言,偏移量序列是[2,2,3,1]。对这个序列做循环变换,生成了置换[2,3,1,2],对应的置换是[C,A,D,B]。

1.3.2 转子的步进机制及异常情况

很多人把转子的步进机制比作里程表。虽然二者有某些相似处,但这样理解是不恰当的。

首先要明确的是:转子的轮转是在每次加密之前,在击键后进行的。

要了解中间和左侧转子是如何运动的,我们必须看看“步进”机制。在每个转子的字母环上,右侧有 26 个凹槽。左侧有一个槽口。传动机构在相邻转子之间使用“T”形推杆,每次击键都会前后移动。当推杆右侧的车轮到达其凹口位置时,推杆“T”的右侧分支落入凹口。然后,左侧分支可以接合左侧转子中的 26 个槽口之一。在下一次击键时,推杆移动两个转子(同样是在被加密之前击键)。当推杆右侧的轮子不在其凹口位置时,字母环的实心部分会阻止推杆与左侧的轮子啮合。 当我们查看右转子和中转子之间的进位时,这种机制会产生与里程表相同的效果。但对于中转子和左转子,情况则有所不同。要了解原因,让我们通过一个示例:假设中间转子距离其凹槽位置 1 个位置,而右侧转子位于其凹槽位置。当按下一个键时,中到右的推杆同时移动右旋翼和中旋翼,正如我们所期望的那样。然后对击键进行加密。现在中间转子处于其槽口位置,因此从左到中间的推杆接合中间转子的槽口和左转子右手侧的 26 个槽口之一。在下一次击键时,中间和左侧转子都移动。右转子在所有击键时移动,因此所有转子一起移动。令人惊讶的效果是中间转子在连续击键时移动了两次,这不是里程表会让我们期待的。

实例:选取转子1,2,3,初始化指针为“EDV”。(槽口分别为:l:Q,m:E,r:V)

由于右转子处在槽口位置,在击键后,右转子带动左转子步进,结束后指针为EEW: 由于中间转子处在槽口位置,在击键后,右转子步进的同时,中间转子带动左转子步进,结束后指针为FFX:

1.4 编程复现

1.4.1 源码

转载自Github上的Enigma模拟器。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/usr/bin/env python

"""enigma.py: Enigma M3 Simulator"""

__author__ = "Kurt Giessel"
__copyright__ = "Copyright 2015, Dennyhill Demolition Company"
__credits__ = ["Kurt Giessel"]
__license__ = "GPL"
__version__ = "0.1.2"
__maintainer__ = "Kurt Giessel"
__email__ = "kurt@giessel.net"
__status__ = "Beta"

#####################################################################################################################
# Variables
alphabet = (
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z')
rotor1 = (
'E', 'K', 'M', 'F', 'L', 'G', 'D', 'Q', 'V', 'Z', 'N', 'T', 'O', 'W', 'Y', 'H', 'X', 'U', 'S', 'P', 'A', 'I', 'B', 'R',
'C', 'J')
rotor2 = (
'A', 'J', 'D', 'K', 'S', 'I', 'R', 'U', 'X', 'B', 'L', 'H', 'W', 'T', 'M', 'C', 'Q', 'G', 'Z', 'N', 'P', 'Y', 'F', 'V',
'O', 'E')
rotor3 = (
'B', 'D', 'F', 'H', 'J', 'L', 'C', 'P', 'R', 'T', 'X', 'V', 'Z', 'N', 'Y', 'E', 'I', 'W', 'G', 'A', 'K', 'M', 'U', 'S',
'Q', 'O')
rotor4 = (
'E', 'S', 'O', 'V', 'P', 'Z', 'J', 'A', 'Y', 'Q', 'U', 'I', 'R', 'H', 'X', 'L', 'N', 'F', 'T', 'G', 'K', 'D', 'C', 'M',
'W', 'B')
rotor5 = (
'V', 'Z', 'B', 'R', 'G', 'I', 'T', 'Y', 'U', 'P', 'S', 'D', 'N', 'H', 'L', 'X', 'A', 'W', 'M', 'J', 'Q', 'O', 'F', 'E',
'C', 'K')
reflector = (
'Y', 'R', 'U', 'H', 'Q', 'S', 'L', 'D', 'P', 'X', 'N', 'G', 'O', 'K', 'M', 'I', 'E', 'B', 'F', 'Z', 'C', 'W', 'V', 'J',
'A', 'T')
plugboard = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z']

rotorAClick = 0
rotorBClick = 0
rotorCClick = 1
encryptedMsg = ''
steckerCount = 1
lettersUsed = ['']


#####################################################################################################################
# Functions

# Plugboard Functions
def doSteckerbrett(stecker1, stecker2): # swap letters in plugboars list

plugboard.remove(stecker1)
plugboard.insert(alphabet.index(stecker2), stecker1)
plugboard.remove(stecker2)
plugboard.insert(alphabet.index(stecker1), stecker2)


def chooseWires(steckers): # choose a pair of letters to cross connect
global ans
global steckerCount # keeps track of how many wires have been used

stecker1 = input('Select first letter of the pair: ').upper()
while lettersUsed.count(stecker1) == 1:
if lettersUsed.count(stecker1) == 1:
print('You have already used %s.' % stecker1)
stecker1 = input('Select first letter of the pair: ').upper()
else:
lettersUsed.append(stecker1)
lettersUsed.append(stecker1)
stecker2 = input('Select next letter of pair: ').upper()
while lettersUsed.count(stecker2) == 1:
if lettersUsed.count(stecker2) == 1:
print('You have already used %s.' % stecker2)
stecker2 = input('Select next letter of the pair: ').upper()
else:
lettersUsed.append(stecker2)
lettersUsed.append(stecker2)

doSteckerbrett(stecker1, stecker2)

if steckers == 1:
print('You have used 1 wire.')
else:
print('You have used %s wires.' % str(steckers))
ans = input('Do you want to configure another pair? (y,n): ').upper()

steckerCount += 1
return


# Configure Rotors Function
def rotorConfig(): # configure each rotor with their initital setting
global rotorA
global rotorASetting
global rotorB
global rotorBNotch
global rotorBSetting
global rotorC
global rotorCNotch
global rotorCSetting

selectRotor('Left', 'rotorA')
rotorA = rotorUsed
rotorASetting = rotorSetting
selectRotor('Center', 'rotorB')
rotorB = rotorUsed
rotorBNotch = notch
rotorBSetting = rotorSetting
selectRotor('Right', 'rotorC')
rotorC = rotorUsed
rotorCNotch = notch
rotorCSetting = rotorSetting
return


# Select Rotors Function
def selectRotor(location, rotor): # select which rotors to use
global rotorUsed
global rotorSetting
global notch
rotorSelect = input('Select %s Rotor (1-5): ' % location)
if rotorSelect == '1':
rotorUsed = rotor1
notch = 17 # notch indicates where the next rotor rotates
elif rotorSelect == '2':
rotorUsed = rotor2
notch = 5
elif rotorSelect == '3':
rotorUsed = rotor3
notch = 22
elif rotorSelect == '4':
rotorUsed = rotor4
notch = 10
elif rotorSelect == '5':
rotorUsed = rotor5
notch = 0
rotorSelectSetting = input('Select Inital Setting for %s Rotor (A-Z): ' % location).upper()
rotorSetting = alphabet.index(rotorSelectSetting)
return


# Get Contact Index Function
def getContactIndex(inputLetter, offset): # get the contact position of the rotor
global contactIndex
contactIndex = alphabet.index(inputLetter) - offset
if contactIndex < 0:
contactIndex += 26
return


# Rotor In Function
def rotorIn(contactIndex, rotor, offset): # get rotor letter for inbound pass
global outputLetter
letterIndex = contactIndex + offset
if letterIndex >= 26:
letterIndex -= 26
outputLetter = rotor[letterIndex]
return


# Reflector Function
def doReflector(contactIndex, reflector): # get reflector letter
global outputLetter
outputLetter = reflector[contactIndex]
return


# rotor Out Function
def rotorOut(contactIndex, rotor, offset): # get rotor letter for outbound pass
global outputLetter
innerLetterIndex = contactIndex + offset
if innerLetterIndex >= 26:
innerLetterIndex -= 26
innerLetter = alphabet[innerLetterIndex]
outerLetterIndex = rotor.index(innerLetter)
outputLetter = alphabet[outerLetterIndex]
return


def doRotors(inputLetter): # get letter after going through all rotors
global rotorsOutLetter
global rotorAClick
global rotorBClick
global rotorCClick

offset = 0

getContactIndex(inputLetter, offset)
offset = rotorCClick + rotorCSetting
if offset >= 26:
offset -= 26
if offset == rotorCNotch:
rotorBClick = 1
rotorIn(contactIndex, rotorC, offset)

getContactIndex(outputLetter, offset)
offset = rotorBClick + rotorBSetting
if offset >= 26:
offset -= 26
if offset == rotorBNotch and rotorBClick == 1:
rotorAClick = 1
rotorIn(contactIndex, rotorB, offset)

getContactIndex(outputLetter, offset)
offset = rotorAClick + rotorASetting
if offset >= 26:
offset -= 26
rotorIn(contactIndex, rotorA, offset)

getContactIndex(outputLetter, offset)
doReflector(contactIndex, reflector)

offset = 0
getContactIndex(outputLetter, offset)
offset = rotorAClick + rotorASetting
if offset >= 26:
offset -= 26
rotorOut(contactIndex, rotorA, offset)

getContactIndex(outputLetter, offset)
offset = rotorBClick + rotorBSetting
if offset >= 26:
offset -= 26
rotorOut(contactIndex, rotorB, offset)

getContactIndex(outputLetter, offset)
offset = rotorCClick + rotorCSetting
if offset >= 26:
offset -= 26
rotorOut(contactIndex, rotorC, offset)

rotorsOutLetter = outputLetter
return


#####################################################################################################################

print('#######################')
print('# Enigma M3 Simulator #')
print('#######################')
print

print('The plugbord cross-connects pairs of letters using wires.')
print('You can use up to 10 wires on the plugboard.')
ans = input('Do you want to configure the plugboard? (y,n): ').upper()

while ans == 'Y' and steckerCount < 10: # you can only choose 10 sets of wires

chooseWires(steckerCount)

print('Now you will select the rotors and their initial settings.')
print('There are 5 rotors to choose from. You will use 3 of them.')
print('Then you will select an initial setting for each rotor.')

rotorConfig()

print('#########################################################################')
print('# The Enigma machine is configured. You are ready to send your message. #')
print('#########################################################################')

while 1 == 1:
keyPress = input('Type a Letter (! to quit): ').upper()
checkKeyPress = alphabet.count(keyPress)
if keyPress == '!':
quit()
while checkKeyPress != 1:
print(keyPress + ' is not a letter. Please type a letter (A-Z)')
keyPress = input('Type a Letter (* to quit): ').upper()
checkKeyPress = alphabet.count(keyPress)
if keyPress == '!':
quit()

# Plugboard In
keyPressIndex = alphabet.index(keyPress)
plugboardInLetter = plugboard[keyPressIndex]
doRotors(plugboardInLetter)

# Plugboard Out
offset = rotorCClick + rotorCSetting
getContactIndex(rotorsOutLetter, offset)
plugboardOutLetter = plugboard[contactIndex]
illuminatedLetter = plugboardOutLetter

# Encrypted Message
encryptedMsg += str(illuminatedLetter)
print('Encrypted Message: %s' % encryptedMsg)

rotorCClick += 1


1.4.2 运行示例

Enigma M3 Simulator

The plugbord cross-connects pairs of letters using wires. You can use up to 10 wires on the plugboard. Do you want to configure the plugboard? (y,n): y Select first letter of the pair: a Select next letter of pair: c You have used 1 wire. Do you want to configure another pair? (y,n): y Select first letter of the pair: b Select next letter of pair: d You have used 2 wires. Do you want to configure another pair? (y,n): n Now you will select the rotors and their initial settings. There are 5 rotors to choose from. You will use 3 of them. Then you will select an initial setting for each rotor. Select Left Rotor (1-5): 1 Select Inital Setting for Left Rotor (A-Z): B Select Center Rotor (1-5): 2 Select Inital Setting for Center Rotor (A-Z): C Select Right Rotor (1-5): 3 Select Inital Setting for Right Rotor (A-Z): D #########################################################################

The Enigma machine is configured. You are ready to send your message.

Type a Letter (! to quit): H Encrypted Message: K Type a Letter (! to quit): E Encrypted Message: KX Type a Letter (! to quit): I Encrypted Message: KXB Type a Letter (! to quit): L Encrypted Message: KXBR Type a Letter (! to quit): H Encrypted Message: KXBRN Type a Letter (! to quit): I Encrypted Message: KXBRNR Type a Letter (! to quit): T Encrypted Message: KXBRNRR Type a Letter (! to quit): L Encrypted Message: KXBRNRRJ Type a Letter (! to quit): E Encrypted Message: KXBRNRRJM Type a Letter (! to quit): R Encrypted Message: KXBRNRRJMV Type a Letter (! to quit): !

Process finished with exit code 0

2 破译

2.1 原理

2.1.1 Cribs与Menus

经过多方考虑,Alan Turing选择用已知明文攻击来破译Enigma。图灵考虑到有可能在已知的,或者猜测的明文,称为Crib,和密文之间建立联系。

但是,如何找到Cribs呢?德军的联络往往以一种老套的方式交流,所以一般来说,猜测明文中一个特定的短语是很容易的。更重要的是,确定明文对应密文的准确位置也是可能的,因为Enigma的加密机制保证了其不会将一个字母加密成自身。天气预报是很有价值的信息来源,比如D-Day德军发出的第一条信息是

1
WETTERVORHERSAGEBISKAYA

假设这条信息包含在如下密文中:

1
…QFZWRWIVTYRESXBFOGKUHQBAISEZ…

要确认Crib对应的是哪个密文,我们把Crib沿着密文移动,直到找到Crib中的任何一个字母对应的密文都不是自身的位置。

Q F Z W R W I V T Y R E S X B F O G K U H Q B A I S E Z
W E T T E R V O R H E R S A G E B I S K A Y A

Figure 1.1

比如,在Figure 1.1中,第一个在WETTERVORHERSAGEBISKAYA中的S的密文是…QFZWRWIVTYRESXBFOGKUHQBAISEZ…中的S,所以我们直到这里并不对应,因为前文已经说过,Enigma的加密机制保证了其不会将一个字母加密成自身。

Q F Z W R W I V T Y R E S X B F O G K U H Q B A I S E Z
W E T T E R V O R H E R S A G E B I S K A Y A

Figure 1.5

在Figure 1.5中,我们终于能看到Crib对应了准确的密文,所以RWIVTYRESXBFOGKUHQBAISE很可能对应的明文是WETTERVORHERSAGEBISKAYA

现在的问题是,在众多转子、插线板设置中,哪一个才是用来加密这段文字的?

Crib和密文的组合顺序可标记如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
R W I V T Y R E S X B F O G K U H Q B A I S E
W E T T E R V O R H E R S A G E B I S K A Y A

所以,如果Crib与密文联系正确的话,我们直到Enigma在特定的位置,会将R加密成W,将W加密成R,我们将这个位置记为1,不同位置会将E加密成W,将W加密成E;将I加密成T,将T加密成I,如下表所示。

img

2.1.2 字母置换循环

回顾一下,Enigma加密的每个过程包括:

  1. 插线板置换
  2. 扰频器加密
  3. 另一组插线板置换

Turing注意到有些置换连接着其他置换,因此可以通过回溯来检验是否正确,或者更直截了当的,证明一种置换肯定不正确。

例如,我们来检验猜想“E通过插线板置换成了K”的正确性。可以看到E在第23步被加密成了A。我们来研究一下这个过程。

首先,E通过插线板置换成了K,K 将被输入到位于相对位置 23 的扰频器,该扰频器将输出其他字母v1。因为我们知道E加密成了A,所以v1通过插线板置换成了A。

我们同时知道,A在第21步被加密成了I。观察这个过程可以发现,由于插线板置换的对称性,A通过插线板置换后一定得到v1,v1被输入到位于相对位置 21 的扰频器并输出其他字母v2。沿用上一步的推导,很容易得到v2通过插线板置换成了I。

以此类推,I通过插线板置换成v2,v2被输入到位于相对位置3的扰频器并输出其他字母v3,v3通过插线板置换成T,T通过插线板置换成v3,v3则被输入到位于相对位置5的扰频器。

现在,考虑当v3输入到位于相对位置5的扰频器的输出。由于T在第五步被加密成了E,我们可以肯定输出的字母通过插线板置换成了E。

假设,当输入的字母是v3,相对位置为5的扰频器输出的字母是J。我们最初的猜测是E通过插线板置换成了K,

但经过一系列连接着的置换后,E通过插线板置换成了J,插线板上的一个字母不会与两个字母置换,因此最初的猜想是错的。

而假设输入的字母是v3,相对位置为5的扰频器输出的字母是K,这与我们最初的猜想一样,即E通过插线板置换成了K一样,那么原猜想基于这样的条件成立,因而有可能是正确的。

img

综上,插线板置换假设成立的充要条件是:通过一系列连接的置换,输出的与输入的相同,与自身形成闭环。

特别地,根据这个理论,我们需要找到符合假设的循环。当Crib-密文对中存在四组循环时,绝大多数转子设置将被排除,从而得到正确的转子设置。

利用Crib-密文对中存在的循环,测试矛盾是否成立正是Turing对Enigma的已知明文攻击的理论基础。

Turing意识到可以将循环置换通过设计电路实现,因而可以将检查转子的顺序和位置是否满足假设的过程通过机器实现。Turing设计的机器名叫Turing bombe。

换言之,bombe 能够检查当前转子的位置和顺序,以及插线板的设置,是否能将明文正确加密为密文。通过查验60种转子顺序,17576种转子位置,bombe能提供详尽的Enigma初始化设置,共包含

107,458,687,327,300,000,000,000

种。就这样,Turing实现了大海捞针般看似不可思议的破译工作,找到了抵抗纳粹的钥匙。

2.2 编程复现

2.2.1 源码1:暴力破解

转载自Github上的Turing bombe模拟器。不能处理有插线板的情况。有待进一步补充。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/usr/bin/env python

"""bombe.py: Turing Bombe Simulator"""

__author__ = "Kurt Giessel"
__copyright__ = "Copyright 2015, Dennyhill Demolition Company"
__credits__ = ["Kurt Giessel"]
__license__ = "GPL"
__version__ = "0.1.0"
__maintainer__ = "Kurt Giessel"
__email__ = "kurt@giessel.net"
__status__ = "Beta"

#####################################################################################################################

import itertools
import time

#####################################################################################################################
# Variables
alphabet = (
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z')
rotor1 = (
'E', 'K', 'M', 'F', 'L', 'G', 'D', 'Q', 'V', 'Z', 'N', 'T', 'O', 'W', 'Y', 'H', 'X', 'U', 'S', 'P', 'A', 'I', 'B', 'R',
'C', 'J', 'Rotor1')
rotor2 = (
'A', 'J', 'D', 'K', 'S', 'I', 'R', 'U', 'X', 'B', 'L', 'H', 'W', 'T', 'M', 'C', 'Q', 'G', 'Z', 'N', 'P', 'Y', 'F', 'V',
'O', 'E', 'Rotor2')
rotor3 = (
'B', 'D', 'F', 'H', 'J', 'L', 'C', 'P', 'R', 'T', 'X', 'V', 'Z', 'N', 'Y', 'E', 'I', 'W', 'G', 'A', 'K', 'M', 'U', 'S',
'Q', 'O', 'Rotor3')
rotor4 = (
'E', 'S', 'O', 'V', 'P', 'Z', 'J', 'A', 'Y', 'Q', 'U', 'I', 'R', 'H', 'X', 'L', 'N', 'F', 'T', 'G', 'K', 'D', 'C', 'M',
'W', 'B', 'Rotor4')
rotor5 = (
'V', 'Z', 'B', 'R', 'G', 'I', 'T', 'Y', 'U', 'P', 'S', 'D', 'N', 'H', 'L', 'X', 'A', 'W', 'M', 'J', 'Q', 'O', 'F', 'E',
'C', 'K', 'Rotor5')
reflector = (
'Y', 'R', 'U', 'H', 'Q', 'S', 'L', 'D', 'P', 'X', 'N', 'G', 'O', 'K', 'M', 'I', 'E', 'B', 'F', 'Z', 'C', 'W', 'V', 'J',
'A', 'T')

#global encryptedTextMsg
#global plainTextMsg


# Configure Rotors Function
def selectRotor_A(i, n): # select which rotors to use
#global rotorUsed
#global notch
rotorUsed=0
rotorSelect = rotorPerm[i][n]
if rotorSelect == 1:
rotorUsed = rotor1
notch = 17
elif rotorSelect == 2:
rotorUsed = rotor2
notch = 5
elif rotorSelect == 3:
rotorUsed = rotor3
notch = 22
elif rotorSelect == 4:
rotorUsed = rotor4
notch = 10
elif rotorSelect == 5:
rotorUsed = rotor5
notch = 0
return rotorUsed

def selectRotor_BorC(i, n): # select which rotors to use
#global rotorUsed
#global notch
rotorUsed=0
notch=0
rotorSelect = rotorPerm[i][n]
if rotorSelect == 1:
rotorUsed = rotor1
notch = 17
elif rotorSelect == 2:
rotorUsed = rotor2
notch = 5
elif rotorSelect == 3:
rotorUsed = rotor3
notch = 22
elif rotorSelect == 4:
rotorUsed = rotor4
notch = 10
elif rotorSelect == 5:
rotorUsed = rotor5
notch = 0
return rotorUsed,notch


# Get Contact Index Function
def getContactIndex(inputLetter, offset): # get the contact position of the rotor
global contactIndex
contactIndex = alphabet.index(inputLetter) - offset
if contactIndex < 0:
contactIndex += 26
return


# Rotor In Function
def rotorIn(contactIndex, rotor, offset): # get rotor letter for inbound pass
global outputLetter
letterIndex = contactIndex + offset
if letterIndex >= 26:
letterIndex -= 26
outputLetter = rotor[letterIndex]
return


# Reflector Function
def doReflector(contactIndex, reflector): # get reflector letter
global outputLetter
outputLetter = reflector[contactIndex]
return


# rotor Out Function
def rotorOut(contactIndex, rotor, offset): # get rotor letter for outbound pass
global outputLetter
innerLetterIndex = contactIndex + offset
if innerLetterIndex >= 26:
innerLetterIndex -= 26
innerLetter = alphabet[innerLetterIndex]
outerLetterIndex = rotor.index(innerLetter)
outputLetter = alphabet[outerLetterIndex]
return


def doRotors(inputLetter, compareIndex): # get rotor letter for outbound pass
global rotorsOutLetter
global rotorAClicked
global rotorBClicked

rotorCClick = compareIndex + 1

offset = 0
getContactIndex(inputLetter, offset)
offset = rotorCClick + rotorCSetting
if offset >= 26:
offset -= 26
if offset == rotorCNotch:
rotorBClicked = True
rotorIn(contactIndex, rotorC, offset)

getContactIndex(outputLetter, offset)
offset = rotorBSetting
if rotorBClicked == True:
offset += 1
if offset >= 26:
offset -= 26
if offset == rotorBNotch and rotorBClicked == True:
rotorAClicked = True
rotorIn(contactIndex, rotorB, offset)

getContactIndex(outputLetter, offset)
offset = rotorASetting
if rotorAClicked == True:
offset += 1
if offset >= 26:
offset -= 26
rotorIn(contactIndex, rotorA, offset)

getContactIndex(outputLetter, offset)
doReflector(contactIndex, reflector)

offset = 0
getContactIndex(outputLetter, offset)
offset = rotorASetting
if rotorAClicked == True:
offset += 1
if offset >= 26:
offset -= 26
rotorOut(contactIndex, rotorA, offset)

getContactIndex(outputLetter, offset)
offset = rotorBSetting
if rotorBClicked == True:
offset += 1
if offset >= 26:
offset -= 26
rotorOut(contactIndex, rotorB, offset)

getContactIndex(outputLetter, offset)
offset = rotorCClick + rotorCSetting
if offset >= 26:
offset -= 26
rotorOut(contactIndex, rotorC, offset)

getContactIndex(outputLetter, offset)
rotorsOutLetter = alphabet[contactIndex]
return


def compareLetter(compareIndex): # compare the plaintext letter with suspected results
global rotorsOutLetter
inputLetter = encryptedTextMsg[compareIndex]
plainTextLetter = plainTextMsg[compareIndex]
doRotors(inputLetter, compareIndex)
print('Trying '+ alphabet[rotorASetting]+ alphabet[rotorBSetting]+ alphabet[rotorCSetting])
if rotorsOutLetter == plainTextLetter:
print('Possible Solution... Checking Next Letter')
compareIndex += 1
if compareIndex == 4: #只要四个字母加密正确,就判定解法正确

print('#####################################################')
print('Solution Found!!!!!')
print('Settings: ' + rotorA[26] + ':' + alphabet[rotorASetting] + ' ' + rotorB[26] + ':' + alphabet[rotorBSetting] + ' ' + rotorC[26] + ':' + alphabet[rotorCSetting])
rAs = rotorASetting + 1
rBs = rotorBSetting + 1
rCs = rotorCSetting + 1
possibleSolutions = i * (rAs * (26 * 26)) + (rBs * 26) + rCs
endTime = time.time()
totalTime = endTime - startTime
print('Checked ' + str(possibleSolutions) + ' possible solutions in ' + str(totalTime) + ' seconds')
print('#####################################################')
quit()
compareLetter(compareIndex)


#####################################################################################################################

print('##########################')
print('# Turing Bombe Simulator #')
print('##########################')


encryptedTextMsg = input('Enter Encrypted Text: ')
plainTextMsg = input('Enter Plain Text: ')

startTime = time.time()

# Check All Rotor Permutations
rotorPerm = list(itertools.permutations([1, 2, 3, 4, 5], 3))

i = 0
for list in rotorPerm:

rotorASetting = 0
rotorBSetting = 0
rotorCSetting = 0

rotorA = selectRotor_A(i, 0)

rotorB,rotorBNotch=selectRotor_BorC(i,1)
rotorC, rotorCNotch = selectRotor_BorC(i, 2)

print('Trying ' + rotorA[26] + ':' + rotorB[26] + ':' + rotorC[26])

i += 1

# Check All Rotor Setting
while 1 == 1:
rotorAClicked = False
rotorBClicked = False
compareIndex = 0
compareLetter(compareIndex)
rotorCSetting += 1
if rotorCSetting == 26:
rotorCSetting = 0
rotorBSetting += 1
if rotorBSetting == 26:
rotorBSetting = 0
rotorASetting += 1
if rotorASetting == 26:
break

参考文献

[1]Mark Stamp,《信息安全原理与实践》,6.2.1

[2]Mark Stamp,《信息安全原理与实践》,6.2.2

[3]Mark Stamp,《信息安全原理与实践》,6.2.2

[4]A.Carlson,Simulating the Enigma cypher machine,http://cse.csusb.edu/ykarant/cryptography/enigma/~andycarlson/enigma/simulating_enigma.html

[5]Kurt Giessel,python_bombe/enigma.py,https://github.com/kgiessel/python_bombe/blob/master/enigma.py

[6]Graham Ellsbury,The Turing Bombe——What is was and how it worked,http://www.ellsbury.com/bombe1.htm

[7]Kurt Giessel,python_bombe/bombe.py,https://github.com/kgiessel/python_bombe/blob/master/bombe.py