破解Enigma机

破解Enigma机

​ 二战历史上著名的Enigma机,这里不再介绍其背景了。

​ 有个电影《模仿游戏》值得一看。

Enigma机构造

  1. 包含26个英文字母的键盘(Keyboard)
  2. 标有26个英文字母的线路接线板(Stecker)
  3. 扰频器组合(Rotors)
  4. 反射器(Reflector)
  5. 包含26个英文字母显示灯的显示灯板(Lightboard)

破解Enigma机_第1张图片

加解密算法

p:明文字符

c:密文字符

S:接线板(含密钥。设使用了 l l l条连接线,密钥个数为: 26 ! / ( ( 26 − 2 l ) ! × l ! × 2 l ) 26!/((26-2l)! \times l! \times 2^l) 26!/((262l)!×l!×2l)

R:扰频器(含密钥。t个扰频器中选择3个,密钥个数为: ( t 3 ) × 2 6 3 ({}_t^3) \times 26^{3} (t3)×263)

T:反射器(固定的,无密钥)

破解Enigma机_第2张图片

破解

将明文与密文联系起来,明密文组合被称作克利巴(Crib), 其实就是明密文对。

可以发现一种特殊的Crib:W加密到E,E加密到T,T加密到W。

破解Enigma机_第3张图片

这种环路的存在,导致了Enigma组件的分割

破解Enigma机_第4张图片

我们可以分别破解扰频器和接线板:

  1. 猜测一个扰频器的密钥S,如:(13,5,7)
  2. 对环路的起始字符进行遍历(a ~ z),依次计算环路上其他字符。
  3. 比较结尾字符是否与起始字符相同。相同,S可能是正确密钥;否则,一定是错误密钥。
  4. 利用上述的破解结果,对接线板密钥进行恢复。注意利用双射关系。
  5. 选取其他明密文对,对所有可能的密钥进行验证,确定出唯一解。

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))

        

你可能感兴趣的:(密码学,密码学,信息安全,加密解密)