PYTHON编程挑战——恩格玛机完成版!
1,介绍
恩格玛机是二战时期德国的一个加密设备,它的原理是替换加密。
以上内容部分来自知乎:https://www.zhihu.com/question/28397034,答主十一点半
什么是替换加密呢?假设我们有一个下文这样的表:
那么,我输入helloworld,替换后的密码就是RUXXAKAFXP
实际上,这样的密码已经很难破解了,但是如果上下文够长,还可以通过统计单词中每个字母出现概率的方式,来判断密码。
如e一定是出现次数最多的字母。如下表是26个字母出现的概率:
这样便被破解了……
不死心的密码工程师们,就又发明了多字母替换密码表,用两个表交替给一篇文章加密。
两个表交替加密,那么e可能被A代替,也可能被L代替,无形中又增加了破解的难度。
假设有一位勤劳的密码师,为了得到一份绝对安全的密码,他不辞劳苦地打算使用七行密码表对明文进行加密,为了方便记忆,他选取了GERMANY作为关键 词。也就是说,密码师将分别使用位于上面方阵中G、E、R、M、A、N、Y行的字母作为密码表对明文中的字母进行循环加密。
由于明文中的每一个字母都会被随机地替换为另外七个不同的字母,所以前面介绍过的频率分析法将不再起作用。这一次,密码师是不是终于得到了一份绝对安全的密文?
答案当然是否定的。破解者很快就发明了新的破解方法。
密码破译者在获得足够长的密文之后,可以寻找密文中重复出现的字母串。以英文为例,在一篇文章中有一些特定的单词例如the、and会反复出现。虽然在密 文中这些单词有6/7的几率会被替换为不同的形式,但如果两个相同单词之间所间隔的字母数刚好是7的倍数的话,它们就会被替换为相同的形式。这是因为替换 第二个单词时,总数为7行的密码表刚好完成了若干个完整的循环。
只要截获足够长的密文,破解者就可以对重复出现的字母串之间的距离进行分析。
假设破解者获得的分析结果是这样的:
在相距21个字母的地方,重复出现了字母串GHI;
在相距77个字母的地方,重复出现了字母串TUIXS;
在相距147个字母的地方,重复出现了字母串OCD;
……
由于字母串重复出现的距离都是7的倍数,破解者可以由此确定,这份密文使用了7行密码表进行加密。如果各位都认真地阅读了前面介绍的单字母替换密码的破解方法,不用我说你们也知道下一步该怎么做了吧?
破解者下一步只需要把密文中位于第1、8、15……位置的字母提取出来,写在一张纸上,组成第一个字母集合。这些字母全部是用第一行密码表进行加密的,虽然他现在还不知道这个密码表究竟是什么。
然后再把密文中位于第2、9、16……位置的字母提取出来,组成第二个字母集合。
……
最后把密文中位于第7、14、21……位置的字母提取出来,组成第七个字母集合。
接下来,破解者只要对这七个字母集合分别进行七次字母频率分析,就可以破解这份多字母替换密码。
面对如此丧心病狂的破解者,无奈的密码师只能仰天长叹:“除非每加密一个字母就更换一次密码表并且永不重复,否则如论如何都逃不过被破解的命运。”
“每加密一个字母就更换一次密码表并且永不重复”理论上讲是可以做到的,只不过要加密一份有一万个字母的明文的话,就需要……呃,一个长达一万行的密码本。这样就产生了密码本比密文本身还要长的尴尬局面。
就算有一位勤劳的密码师愿意花几个小时时间制作这样一份密文,密文的接收者也需要花同样长的时间将密文转化成明文,这种低效率的操作方式将大大限制密码的实用性。
如果传送者和接收者之间存在大量的信息交换,那么制造和分发数量如此举得的密钥也将是不切实际的。在军队中,每天都有成千上万条信息在各地之间传递。如果为每一条信息中的每一个字母都创造一个随机密码表的话,可能每天都会消耗掉一个厚几百页的密钥本。如何制造出这么多的随机密钥,如何将这些密钥及时发送到全军各地,如何让全军中的操作员在发送和接收的时候都保持在密码本的同一位置,这些都会是难以解决的问题。(谢谢 @申屠谦夏 指出了原文中的一个错误)
综上所述,“每加密一个字母就更换一次密码表并且永不重复”的替换密码已经超出了人力所及的范围。
不过,人类做不到的事情,不代表机器也无法做到。
在上面这张图中,恩格玛机的四个主要部件被标示了出来,它们分别是:
键盘(Keyboard):这个没什么好解释的,输入密码用的。
灯盘(Lampboard):在键盘上输入一个字母后,灯盘上会有一个字母亮起来,代表经过加密之后的字母。
转子(Rotor):这个是进行加密的部件,具体原理后面解释。
插线板(Plugboard):这是在转子进行加密后,为了进一步提高安全性而增加的装置。你现在只需要知道有这么个东西就好了,具体原理后面会解释。
要想制造出一台在军队中大规模使用的密码机,除了保证密码的复杂程度之外,同时还必须保证操作的简易性,最好随便一个普通士兵在简单训练后都可以马上进行操作。
恩格玛机的伟大之处就在于它在进行高度复杂的替换加密的同时,操作的简易性也几乎做到了极致。
当一名德军军官将一台恩格玛机设置好之后(它的设置也简单到不像话,后面介绍),只需要随便叫来一个小兵:
“二等兵汉斯!过来把这封电报转成密文!”
“报告长官,我不会什么加密,我小学只读到了四年级。”
“过来坐下,你把这份电报一个字母一个字母的敲到键盘上。每敲一个字母,灯盘上就有一个字母亮起,把亮起的这个字母记录下来。重复这个动作,直到敲完整份电报,然后把得到的密文送到发报室去。还有其他问题吗?很好,开始工作!”
你看,不用去德国陆军学院修满20个学时的初级密码学,也不用考过德语四六级,恩格玛机就是这么简单易用老少咸宜。
讲完操作方法,我们再来看一下恩格玛机的核心部件——转子:
图片的左边是一个完整的转子,右边这些零件是这个转子拆开后内部的样子。
转子的工作原理其实非常简单。它的左右两侧各有26个点位,分别代表A-Z这26个字母。信号从一边进去,从另一边出来。但是在制造过程中,位于转子左右两边的26个字母点位被刻意交叉连接,以达到字母替换的目的。例如转子右边代表字母A的点位并没有与左边的A点位相连接,而是被替换成了另外一个字母例如E。图片右边那些一条条的绿色的线路就是连接左右两边点位的电线。
下面是两张侧视图,可以更清楚的看到位于转子两侧的26个点位:
也就是说,当代表字母A的信号从右侧进入并通过转子后,从左侧出来的时候被替换成了字母E。所以说,单个转子对输入内容所做的本质上是一次单字母替换加密。由于一个转子被制造出来之后,两侧点位的对应连接关系就无法改变,单个转子只能提供一个固定不变的密码表。
我们前面讲过,单字母替换密码是替换密码中最初级的形式,只要使用字母频率分析就可以轻易破解。很明显,仅仅使用一个转子进行加密是根本行不通的。德国人当然也非常清楚这一点,所以他们在恩格玛机上使用了三个串联在一起的转子,就像这样:
三个转子被串联起来之后,输入的字母被依次这三个转子进行多次替换。在这里大家先不要去过多琢磨这个字母究竟被进行了多少次替换,而是要透过现象看到本质。这个字母在输出到灯盘之前,无论是被替换了五次还是一百次,对于使用者来说,输入26个字母中的每一个字母,都只会得到一个与其对应的、不会改变的替换结果。也就是说,单纯地将三个转子串联起来之后,它们还是只能提供一个固定不变的密码表。
但是,当德国人在这三个转子上加入一个新的特性后,它们就可以做到密码师们渴望的每加密一个字母就更换一次密码表的效果。这个新的特性就是:
每输入一个字母之后,第一个转子都会自动转动一格。当第一个转子转完一圈后,会带动第二个转子转动一格。同理,第二个转子在转到特定的位置后,会带动第三个转子转动。
由于两个转子之间的连接是通过转子上26个金属点之间的接触来实现的,所以转子转动一次后,整个系统的信号通路就会变换为另外一种组合。
由于每个转子都有26中可能的位置,所以三个转子一共可以提供26X26X26=17576个不同的密码表。这个数字已经相当可观了,但德国人还是不满足,又把三个转子设计成可以互相交换位置的形式。三个转子有六种不同的排列方式,所以密码表的数量又增加到了17576X6=105456,也就是大约十万个。德国人还是不满足,又增加了上面图片中的插线板,将密码数量进一步增大了1000亿倍(插线板在python中无非是固定替换,所以我的程序就没有写)。
在介绍插线板之前,我们把转子的部分讲完。
一个字母A从键盘被输入之后,依次被三个转子进行三次替换然后到达反射器(红色路径),在反射器这里又被替换成另外一个字母(绿色路径),接着又沿着一条和来时不同的路径(蓝色路径),然后输出最终的加密结果,即字母G。
这个反射器的加入赋予了恩格玛机两个非常非常非常重要的性质:
性质一:反射器使得恩格玛机的加密过程是自反的。也就是说,如果输入字母A得到字母G,那么在机器配置不变的情况下,输入字母G一定会得到字母A。
性质二:一个字母加密后的输出结果绝不会是它自身。
如果你能自己得出以上两个结论,请跳过下面的补充说明继续阅读。如果无法得出,也不要灰心,请阅读下面的补充说明。
====================补充说明开始====================
性质一的推导:连接转子正反两面的电线是固定不变的。转子不转动的话,他们相互之间的连接关系也不会改变。换句话说,加密和解密路径都是唯一的,绝不会从中再伸出一条岔路。反射器的内部结构也是固定不变的,意味着绿色的路径也是固定不变的。既然三条路径都是固定不变的,那么信号沿着加密路径进入转子,必然会沿着解密路径出来。
性质二的推导:我们用反证法来证明。如果想要让一个字母的加密结果是它自身,那么这个字母的信号沿着红色路径到达反射器后,必须再次沿着红色路径返回才行。而这与反射器的工作原理相矛盾,因为反射器的作用就是将输入的信号换一个点位后再输出,以确保其沿着不同的路径返回。
====================补充说明结束====================
为什么说这两个性质非常非常非常重要呢?
性质一这个牛逼的特性意味着恩格玛机不但是加密机,同时也是解密机。也就是说,将明文输入恩格玛机变成密文后,只要把另外一台机器调到初始配置再将密文输入,输出的结果将直接就是明文!真正做到了从八岁到八十岁都可以毫无障碍的使用。
=======================恩格玛机的介绍结束,程序开始=========================
我的程序中,我实现了3转子,转子1每个字母转动一次,转子2当转子1转一整圈时转一次,转子3当转子2转一整圈时转动一次。
所以,如果你的密码小于17576个字符,意味着无法通过观察概率的方法来破解。
废话少说,放码过来:
# -*- coding:utf-8 -*- # 本代码的目的是通过python实现德军二战时期的密码机:恩格玛 import re import string def simple_replace(password, replace_word1, replace_word2, replace_word3): # 加密的主函数 count = 0 # 设置计数器 new_pass = '' # 设置一个空字符串准备接收密码 ori_table = 'abcdefghijklmnopqrstuvwxyz' # 原始的字符串,用来建立映射表 for obj in password: # 开始拆解原字符串 table1 = str.maketrans(ori_table, replace_word1) # 建立转子1的映射表 table2 = str.maketrans(ori_table, replace_word2) # 建立转子2的映射表 table3 = str.maketrans(ori_table, replace_word3) # 建立转子3的映射表 new_obj = str.translate(obj, table1) # 把obj通过转子1转换 new_obj = str.translate(new_obj, table2) # obj通过转子2 new_obj = str.translate(new_obj, table3) # obj通过转子3 new_obj = reverse_word(new_obj) # 进入自反器,得到自反值 reverse_table1 = str.maketrans(replace_word1, ori_table) # 增加自反出去的对应表,反向解译 reverse_table2 = str.maketrans(replace_word2, ori_table) reverse_table3 = str.maketrans(replace_word3, ori_table) new_obj = str.translate(new_obj, reverse_table3) # new_obj再赋值,反向解译通过转子3 new_obj = str.translate(new_obj, reverse_table2) # 通过转子2 new_obj = str.translate(new_obj, reverse_table1) # 通过转子1 new_pass += new_obj # 返回的密码增加一个new_obj replace_word1 = rotors(replace_word1) # 转子1每个字符都转动一次 count += 1 # 计数器增加1 if count % 676 == 0: # 如果模676为0,那么转子3转动一次(因为转子2已经转动了一整圈) replace_word3 = rotors(replace_word3) elif count % 26 == 0: # 如果模26为0,那么转子2转动一次(因为转子1已经转动了一整圈) replace_word2 = rotors(replace_word2) return new_pass # 返回新的已经被转子加密的密码 # 单独把判断写成一个函数吧,这样比较好区分 def is_str(password, replace_word1, replace_word2, replace_word3): # 判断的函数 an = re.match('[a-z]+$', password) # 当时的enigma机是没有空格的,所以这里要求输入的明文也必须是小写字母 if not type(password) == type(replace_word1) == type(replace_word2) == type(replace_word3) == type('a'): print('密码必须是字符串!') return False elif not an: print('字符串只能包含小写字母!') return False elif not len(replace_word1) == len(replace_word2) == len(replace_word3) == 26: print('替换码必须为26个字母!') return False else: return True # 修正了函数的写法,增加了一个返回true的选项 def rotors(replace_word): # 转子转动的函数,每调用一次,就把转子前面第一个字母移动到最后 return replace_word[1:] + replace_word[0] # 还没有自反器呢!加密之后无法解密,是何其的蛋疼! # 自反器很好设置的,只要设置一个字典,保证所有字母(26个)两两对应就可以了,怎么对应,你说了算! def reverse_word(word): dic = {'a': 'n', 'b': 'o', 'c': 'p', 'd': 'q', 'e': 'r', 'f': 's', 'g': 't', 'h': 'u', 'i': 'v', 'j': 'w', 'k': 'x', 'l': 'y', 'm': 'z', 'n': 'a', 'o': 'b', 'p': 'c', 'q': 'd', 'r': 'e', 's': 'f', 't': 'g', 'u': 'h', 'v': 'i', 'w': 'j', 'x': 'k', 'y': 'l', 'z': 'm'} return dic[word] while True: a_password = input('请输入明文加密或密文解密:') r_password1 = 'qwertyuiopasdfghjklzxcvbnm' # 转子1,自己设置即可 r_password2 = 'asdfqwerzxcvtyuiopghjklbnm' # 转子2,自己设置即可 r_password3 = 'poiuytrewqasdfghjklmnbvcxz' # 转子3,自己设置即可 if is_str(a_password, r_password1, r_password2, r_password3): print('密文/明文如下:', simple_replace(a_password, r_password1, r_password2, r_password3))