最近笔者接触到了Cypher这款游戏,玩法很简单,就是通过文字、图片等各种表达手段组成的谜面,猜一段英文,算是初步接触了密码学的一些知识。游戏中提到了很多类型的密码,其中Enigma密码机就是单独一种,在电影《模仿游戏》中,夏洛克.福尔摩斯费尽心力破解的德军密码,也就出自此密码机之手。在游戏里,有一道题目便是,需要根据Enigma密码机的初始设置,破译一段密文,得到明文。没办法,为了解出来这道题,只能发挥程序员的职业本性,写一段程序来跑一下了。
今天,笔者就分享一下自己用python实现的Enigma密码机,虽然代码非常粗糙,有很多优化空间,但整体逻辑比较清晰,尤其是解Cypher游戏里的题目,已经够用了。
简单来讲,一个Engima密码机是分层的,包含以下几个部分:
第一层是Keyboard键盘,是输入输出的部分,输入26个字母,输出的字母可能是26个字母之一。从打字人员的视角,按下键盘上某个字母之后,经过加密,对应密文的字母就会亮起来。
第二层是Plugboard接线板,接线板本质上会把一些字母的输入输出做交换,比如A和B两个字母接起来了,那么输入A输出就是B,反之输入B输出就是A,是一个很简单的置换密码。
第三层叫做Scrambler扰码器,在Enigma构成里也叫做Rotor转子,是整个Enigma密码机最核心的加密部分。虽然扰码器的输入是26个字母,输出也是26个字母的另一种排列,但其复杂度在于,每打一个字母,转子会转一下。比方说转子是ABCD对应DCBA,现在你输一个A,转子出来一个D,但下一次你输A就不是D了,而是C,这是因为转子转了一下,变成BCDA对应CBAD,你输A其实一直对应转子第一个位置,下一次就变成B的位置了,所以就会出来C。所以说,有了这个Scrambler,加密会变得更加复杂,而且转子起始位置不同的话,出来的结果也会不一样。
除此之外,转子还可以叠加,比如叠3个转子,第1个转子转满一圈,第2个转子转1次,第2个转子转满1圈,第3个转子转1次。这样叠下来,复杂度就更高了。如果不了解扰码器的字母映射,不了解转子起始位置,基本破解不出来。
最后一层是Reflector反射器,是Enigma构成里最鬼才的设计。有了这个东西,Enigma就成了自对称加密,也就是说,你输入明文,可能出来一组密文,但你复位到初始设置之后,输入密文,就会把最开始的明文输出出来。Reflector本身也是和Plugboard一样的接线板设计,但在整个加密过程中只经过一次,而不像其他部分经过两次。整个加密步骤是这样的:
- Keyboard输入1个字母
- 第1个转子转一下,可能带动后面的转子转
- 经过Plugboard,看是从当前位置出口输出,还是从某个接线位置的出口输出
- 逐层经过Scrambler转子1~3,从对应位置输入,从映射到的字母的当前位置输出
- 经过Reflector,从Scrambler输出的位置输入,从对应字母的当前位置输出
- 逐个反向经过Scrambler转子3~1,从上一个输出的位置输入,从映射到的字母的当前位置输出
- 反向经过Plugboard,再看是从当前位置返回给Keyboard,还是某个接线的位置返回
- 反向经过Keyboard,输出对应位置的字母
所以整个逻辑捋下来,Enigma密码机重点并非字母之间的映射,而是不同层次之间接线扣子索引位置的映射。而且,当你输入时,其实输入到输出的全加密过程的连线已经连好了,所以反过来也是一样,你输入密文,也会跟着这条连线,最终走到明文对应的字母。
捋完了逻辑,就可以写代码了,可以用简单的面向对象方式,把每个组件定义清楚。首先是PlugBoard,实现比较简单的字母位置映射:
1 | def to_chararray(o): |
然后是Scrambler(Rotor),需要实现正反向输入输出,以及重制到某个位置(字母)的功能:
1 | def rotate_array(arr: list, direction: int): |
然后是Reflector,找到output对应位置的字母,反射到input中接线字母的位置:
1 | class Reflector(object): |
最后组装成整个Enigma机:
1 | class Enigma(object): |
有了这些代码之后,攻克Cypher游戏中的关卡也就变得非常容易了。
1 | def debug(): |