二战历史上著名的Enigma机,这里不再介绍其背景了。
有个电影《模仿游戏》值得一看。
- 包含26个英文字母的键盘(Keyboard)
- 标有26个英文字母的线路接线板(Stecker)
- 扰频器组合(Rotors)
- 反射器(Reflector)
- 包含26个英文字母显示灯的显示灯板(Lightboard)
p:明文字符
c:密文字符
S:接线板(含密钥。设使用了 l l l条连接线,密钥个数为: 26 ! / ( ( 26 − 2 l ) ! × l ! × 2 l ) 26!/((26-2l)! \times l! \times 2^l) 26!/((26−2l)!×l!×2l))
R:扰频器(含密钥。t个扰频器中选择3个,密钥个数为: ( t 3 ) × 2 6 3 ({}_t^3) \times 26^{3} (t3)×263)
T:反射器(固定的,无密钥)
将明文与密文联系起来,明密文组合被称作克利巴(Crib), 其实就是明密文对。
可以发现一种特殊的Crib:W加密到E,E加密到T,T加密到W。
这种环路的存在,导致了Enigma组件的分割:
我们可以分别破解扰频器和接线板:
- 猜测一个扰频器的密钥S,如:(13,5,7)
- 对环路的起始字符进行遍历(a ~ z),依次计算环路上其他字符。
- 比较结尾字符是否与起始字符相同。相同,S可能是正确密钥;否则,一定是错误密钥。
- 利用上述的破解结果,对接线板密钥进行恢复。注意利用双射关系。
- 选取其他明密文对,对所有可能的密钥进行验证,确定出唯一解。
Enigma破解的时间复杂度:扰频器+接线板。
通过分割,将原本的相乘变为相加!
#encoding=utf-8
# 恩格玛机
import numpy as np
import random as rd
import time
import itertools as it #迭代器,可产生笛卡尔积
class Enigma:
def SetP(self,l,connect):
'''
设置接线器
l: 接线数目
connect: 置换列表
'''
self.l = l
self.connect = connect
return
def SetR(self,R1,R2,R3,key):
'''
设置三个转子,R是26长的(0-25)列表
设置密码,key是三元组
'''
self.key = key
self.R1 = R1 #正表
self.R2 = R2
self.R3 = R3
self.RR1 = self.GetInverse(R1) #逆表
self.RR2 = self.GetInverse(R2)
self.RR3 = self.GetInverse(R3)
self.p1 = key[0] #三个转子的指针
self.p2 = key[1]
self.p3 = key[2]
return
def GetInverse(self,lst):
'获得逆表,lst内容是0~size-1'
size = len(lst)
res = [0 for i in range(size)]
for i,j in enumerate(lst):
res[j] = i
return res
def SetT(self,connect):
'''
设置反射器,T是26长的(0-25)列表
'''
self.connect2 = connect
return
def P(self,num):
'接线器,逆接线器'
return self.connect[num]
def R(self,num):
'转子'
num = self.R1[(self.p1+num)%26]-self.p1
num = self.R2[(self.p2+num)%26]-self.p2
num = self.R3[(self.p3+num)%26]-self.p3
return num%26
def RInverse(self,num):
'逆转子'
num = self.RR3[(num+self.p3)%26]-self.p3
num = self.RR2[(num+self.p2)%26]-self.p2
num = self.RR1[(num+self.p1)%26]-self.p1
return num%26
def T(self,num):
'反射器,逆反射器'
return self.connect2[num]
def EncChar(self,num):
'加密,解密: num为0~25的数字'
num = self.P(self.RInverse(self.T(self.R(self.P(num)))))
self.p1,self.p2,self.p3 = self.Add((self.p1,self.p2,self.p3),-1) #转动一次
return num
def Enc(self,string):
'加解密,输入输出字符串'
res = ''
string = string.lower()
a = ord('a')
for ch in string:
num = ord(ch)-a
res += chr(a+self.EncChar(num))
return res
def Enc2(self,loops):
'''
用环绕过接线器,破解key
loops: 环的位置,如:((1,2,4,6),(3,7,8))
返回:[(key),([loop11,loop12,loop13],[loop21,loop22])]
'''
ret = []
size = len(loops)
for p1 in range(26):
for p2 in range(26):
for p3 in range(26):
tmp2 = [] #多个环,[[[(1,2),(2,1)],[(1,2),(2,1)]],[[],[]]]
for loop in loops:
tmp = [] #某个环上的多种可能的环
for ch in range(26):
res = []
ch1 = ch
for i in loop:
ch2 = self.Enc4(ch1,self.Add((p1,p2,p3),(-1)*i))
res += [(ch1,ch2)]
ch1 = ch2
if ch == ch2: #找到了
tmp += [res]
if tmp == []: #没有环
break
tmp2 += [tmp]
else: #如果每个环都存在
ret += [((p1,p2,p3),tuple(tmp2))]
return ret
def Enc3(self,string):
'跟踪转化过程'
res = []
string = string.lower()
a = ord('a')
for ch in string:
num = ord(ch)-a
c1 = self.P(num)
c2 = self.RInverse(self.T(self.R(c1)))
c3 = self.P(c2)
res += [(num,c1,c2,c3)]
self.p1,self.p2,self.p3 = self.Add((self.p1,self.p2,self.p3),-1) #转动一次
return res
def Enc4(self,ch,key):
'去除插线板'
self.p1,self.p2,self.p3 = key
num = self.RInverse(self.T(self.R(ch)))
return num
def Add(self,key,num):
p1,p2,p3 = key
pp1 = p1+num
pp2 = p2 + pp1//26
pp3 = p3 + pp2//26
return (pp1%26,pp2%26,pp3%26)
def choose(self,mayberes,p,c,loops,start,l):
'''
挑选合适的密钥及插线板
res:密钥和环
p:明文;c:密文
loops:环的位置
start:明文的起始偏移
l:插线板接线数
'''
res = []
p = chr2num(p)
c = chr2num(c)
size = len(p)
size2 = len(loops)
pc = [[] for i in range(size)]
for i in range(size):
pc[i] = (p[i],c[i])
for t in mayberes:
tmp = [[-1,-1] for i in range(size)]
for tt in it.product(*t[1]): #一种环的组合
for i in range(size2):
size3 = len(tt[i])
for j in range(size3):
tmp[loops[i][j]-start] = tt[i][j]
for k in range(size):
if tmp[k] == [-1,-1]: #根据相等关系补全字母对
# 在明密文中找相等字符
p1 = findall(p,p[k])
p2 = findall(c,c[k])
p1.remove(k)
p2.remove(k)
tag = -1
if len(p1)>=1:
tmp[k][0] = tmp[p1[0]][0]
tag = 0
if len(p2)>=1:
tmp[k][0] = tmp[p2[0]][0]
if tag == 0:
tag = 2 #补全了
else:
tag = 1
# 在过插线板后的字母中找相等字符
if tag == -1:
for i in range(size2):
size3 = len(tt[i])
for j in range(size3):
if p[k] == tt[i][j][0]:
tmp[k][0] = p[loops[i][j]-start]
tag = 0
if p[k] == tt[i][j][1]:
tmp[k][0] = c[loops[i][j]-start]
tag = 0
if c[k] == tt[i][j][0]:
tmp[k][1] = p[loops[i][j]-start]
if tag == 0:
tag = 2
else:
tag = 1
if c[k] == tt[i][j][1]:
tmp[k][1] = c[loops[i][j]-start]
if tag == 0:
tag = 2
else:
tag = 1
# 进一步处理缺失
if tag == 0: #仅密文过插线板缺失
self.p1,self.p2,self.p3 = self.Add(t[0],(-1)*(k+start))
tmp[k][1] = self.RInverse(self.T(self.R(tmp[k][0])))
if tag == 1: #仅明文过插线板缺失
self.p1,self.p2,self.p3 = self.Add(t[0],(-1)*(k+start))
tmp[k][0] = self.RInverse(self.T(self.R(tmp[k][1])))
if tag == 2: #补全了,看看对不对
self.p1,self.p2,self.p3 = self.Add(t[0],(-1)*(k+start))
if tmp[k][1] != self.RInverse(self.T(self.R(tmp[k][0]))):
break #不对,跳出
if tag == -1:
tmp[k][0] = p[k] #为了减少插线(不太严谨,就这样吧,简单点)
self.p1,self.p2,self.p3 = self.Add(t[0],(-1)*(k+start))
tmp[k][1] = self.RInverse(self.T(self.R(tmp[k][0])))
else: #没有break
M = np.zeros((26,26)) #插线板矩阵
for j in range(size):
M[pc[j][0],tmp[j][0]] = 1
M[tmp[j][0],pc[j][0]] = 1
M[pc[j][1],tmp[j][1]] = 1
M[tmp[j][1],pc[j][1]] = 1
s0 = np.sum(M,0)
s1 = np.sum(M,1)
if (s0>=2).any() or (s1>=2).any(): #插线板不是双射
continue
cnt = [i for i in range(26)]
for k in range(26):
for j in range(26):
if M[k,j] == 1:
cnt[k]=j
break
count = 0
cnt2 = cnt.copy()
for k in range(26): #找逆置对数
if cnt2[k] != k:
cnt2[cnt2[k]] = cnt2[k]
cnt2[k] = k
count += 1
if count <= l:
res += [(t[0],cnt)]
return res
def chr2num(string):
string = string.lower()
size = len(string)
res = [0 for i in range(size)]
a = ord('a')
for i in range(size):
res[i] = ord(string[i])-a
return res
def num2chr(lst):
size = len(lst)
a = ord('a')
res = ''
for i in range(size):
res += chr(lst[i]+a)
return res
def ReplaceListGen1(len,pairs):
'生成len长的含pairs对置换的列表'
res = [i for i in range(len)]
stat = [i for i in range(len)]
for i in range(pairs):
a,b = rd.sample(stat, 2)
stat.remove(a)
stat.remove(b)
res[a] = b
res[b] = a
return res
def ReplaceListGen2(len):
'生成len长的随机置换列表'
res = [0 for i in range(len)]
stat = [i for i in range(len)]
for i in range(len):
a = rd.sample(stat, 1)[0]
stat.remove(a)
res[i] = a
return res
def findall(main,sub):
res = []
size = len(main)
for i in range(size):
if main[i] == sub:
res += [i]
return res
def findloop(p,c):
'找环'
p = p.lower()
c = c.lower()
M = np.zeros((26,26))
size = len(p)
for i in range(size):
M[ord(p[i])-ord('a'),ord(c[i])-ord('a')] = 1
n = min(len(p),26)
MM = M
MMM = M
for i in range(n-1):
MM = MM@M
MMM += MM
res = []
for i in range(26):
if MM[i,i] >= 1:
res += [chr(i+ord('a'))]
return res
# 对照表
# 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
# 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 25
# 明文:-BLESSYOU
# 密文:DLSOYGBUEKEN
# (B,L,S,Y),(1,2,4,6)
# (E,O,U),(3,7,8)
# ((1, 2, 4, 6), (3, 7, 8))
if __name__=='__main__':
l = 6
# 随机数
#connect = ReplaceListGen1(26,l)
#A = ReplaceListGen2(26)
#B = ReplaceListGen2(26)
#C = ReplaceListGen2(26)
#connect2 = ReplaceListGen1(26,13)
#key = [rd.randint(0,25) for i in range(3)]
# PPT上的转子
A = [0, 18, 24, 10, 12, 20, 8, 6, 14, 2, 11, 15, 22, 3, 25, 7, 13, 17, 1, 5, 23, 9, 16, 21, 19, 4]
B = [0, 10, 4, 2, 8, 1, 18, 20, 22, 19, 13, 6, 17, 5, 3, 9, 24, 14, 12, 25, 21, 11, 7, 16, 15, 23]
C = [24, 22, 7, 10, 12, 19, 23, 0, 14, 6, 9, 15, 8, 25, 20, 17, 3, 1, 18, 4, 21, 5, 11, 13, 16, 2]
connect2 = [10, 20, 14, 8, 25, 15, 16, 21, 3, 18, 0, 23, 13, 12, 2, 5, 6, 19, 9, 17, 1, 7, 24, 11, 22, 4]
#####################################################################################
#验证一种解
# (key,connect,A,B,C,connect2)
key = (25, 12, 16) #(0, 13, 16)-1
connect = [1, 0, 2, 4, 3, 5, 13, 7, 8, 9, 24, 11, 20, 6, 14, 15, 16, 17, 22, 19, 12, 21, 18, 23, 10, 25]
#测试加解密正确性
#key = (0,13,23) #从0开始
#connect = [0,1,11,3,15,5,24,7,8,9,10,2,20,13,14,4,16,17,23,19,12,21,22,18,6,25]
#####################################################################################
print('插线板:\t',connect)
print('转子1:\t',A)
print('转子2:\t',B)
print('转子3:\t',C)
print('反射器:\t',connect2)
print('密钥:\t',key)
eg = Enigma()
eg.SetP(l,connect)
eg.SetR(A,B,C,key)
eg.SetT(connect2)
#print('逆转子1:',eg.RR1)
#print('逆转子2:',eg.RR2)
#print('逆转子3:',eg.RR3)
m = 'blessyou'
c = eg.Enc(m) #加密
eg.SetR(A,B,C,key)
p = eg.Enc(c) #解密
print('',m,c,p,sep='\n',end='\n\n')
eg.SetR(A,B,C,key)
print(eg.Enc3(m)) #查看转化过程
a = "BLESSYOU"
b = "LSOYGBUE"
print('\nloops: ',a,b,findloop(a,b),end='\n\n')
# (B,L,S,Y),(1,2,4,6)
# (E,O,U),(3,7,8)
# loops = ((1,2,4,6),(3,7,8))
loops = ((1,2,4,6),(3,7,8))
#loops = eval(input('输入环:'))
start = time.clock()
res = eg.Enc2(loops)
end = time.clock()
print('可能的密钥及其形成的环(前10个):\n',res[0:10])
print('用时:',end-start,'s,共计',len(res),'种可能的密钥')
with open('./tmp1.txt','w') as fout:
fout.write(str(res))
#with open('./tmp1.txt','r') as fin:
# res = eval(fin.read())
start = time.clock()
res2 = eg.choose(res,'BLESSYOU','LSOYGBUE',loops,1,l)
end = time.clock()
print('\n可能的密钥及对应的接线器(前10个):\n',res2[0:10])
print('用时:',end-start,'s,共计',len(res2),'种可能的密钥')
with open('./tmp2.txt','w') as fout:
fout.write(str(res2))