【日常随笔】Enigma密码机的原理及python代码实现

本文搬运自笔者个人CSDN博客:【Python随笔】Enigma密码机的原理及python代码实现,欢迎大家关注!

最近笔者接触到了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,实现比较简单的字母位置映射:

python 复制代码
def to_chararray(o):
    if isinstance(o, list):
        return o
    return [c for c in str(o)]
    

def alphabetical_letters():
    return to_chararray('abcdefghijklmnopqrstuvwxyz')


class Plugboard(object):
    def __init__(self):  # should reflect
        self.inputs = alphabetical_letters()
        self.outputs = alphabetical_letters()
        self.mapping = {}

    def plug_one(self, c1, c2):
        idx1, idx2 = self.inputs.index(c1), self.outputs.index(c2)
        self.mapping[idx1] = idx2
        self.mapping[idx2] = idx1

    def plug_many(self, pairs):
        for c1, c2 in pairs:
            self.plug_one(c1, c2)

    def proxy(self, idx):  # inputs/outputs are the same, so forward & backward no differs
        if idx in self.mapping:
            return self.mapping[idx]
        return idx

然后是Scrambler(Rotor),需要实现正反向输入输出,以及重制到某个位置(字母)的功能:

python 复制代码
def rotate_array(arr: list, direction: int):
    if direction > 0:  # right
        final = arr[-1]
        for i in range(len(arr) - 1, 0, -1):
            arr[i] = arr[i - 1]
        arr[0] = final
    elif direction < 0:  # left
        final = arr[0]
        for i in range(len(arr) - 1):
            arr[i] = arr[i + 1]
        arr[-1] = final


class Scrambler(object):
    def __init__(self, inputs, outputs):
        self.inputs = to_chararray(inputs)
        self.outputs = to_chararray(outputs)
        self.direction = -1 # rotate left
        self.rotate_cnt = 0

    def rotate(self, cnt=1):
        for _ in range(cnt):
            rotate_array(self.inputs, self.direction)
            rotate_array(self.outputs, self.direction)
            self.rotate_cnt += 1

    def reset_to_idx(self, idx):
        for _ in range(idx):
            self.rotate() # 整个都要转,不是一边转
        self.rotate_cnt = 0

    def reset_to_char(self, c):
        while self.inputs[0] != c:
            self.rotate() # 看input的字母来转
        self.rotate_cnt = 0

    def forward(self, idx):
        c = self.inputs[idx]
        return self.outputs.index(c)

    def backward(self, idx):
        c = self.outputs[idx]
        return self.inputs.index(c)

然后是Reflector,找到output对应位置的字母,反射到input中接线字母的位置:

python 复制代码
class Reflector(object):
    def __init__(self, inputs, outputs):
        self.inputs = to_chararray(inputs)
        self.outputs = to_chararray(outputs)

    def reflect(self, idx):
        c = self.outputs[idx]
        return self.inputs.index(c)

最后组装成整个Enigma机:

python 复制代码
class Enigma(object):
    def __init__(self, plugboard=None, scramblers=None, reflector=None):
        self.keyboard = alphabetical_letters()
        self.plugboard: Plugboard = plugboard
        self.scramblers: List[Scrambler] = scramblers
        self.reflector: Reflector = reflector

    def _rotate_scramblers(self):
        for i in range(len(self.scramblers)):
            if i == 0 or (self.scramblers[i-1].rotate_cnt > 0 and
                          self.scramblers[i-1].rotate_cnt % len(self.keyboard) == 0):
                self.scramblers[i].rotate()

    def encode_string(self, s):
        chars = []
        for c in s:
            # input with idx
            idx = self.keyboard.index(c)

            # plugboard forward
            if self.plugboard:
                idx = self.plugboard.proxy(idx)

            # scramblers forward
            if self.scramblers:
                self._rotate_scramblers()
                for scrambler in self.scramblers:
                    idx = scrambler.forward(idx)

            # reflect and backward
            if self.reflector:
                idx = self.reflector.reflect(idx)

                if self.scramblers:
                    scramblers_cnt = len(self.scramblers)
                    for i in range(scramblers_cnt):
                        scrambler = self.scramblers[scramblers_cnt-1-i]
                        idx = scrambler.backward(idx)

                if self.plugboard:
                    idx = self.plugboard.proxy(idx)

            # output char of final idx
            chars.append(self.keyboard[idx])

        return ''.join(chars)

有了这些代码之后,攻克Cypher游戏中的关卡也就变得非常容易了。

python 复制代码
def debug():
    input_string = 'gyhrvflrxy'

    plugboard = Plugboard()
    plugboard.plug_many([
        ('a', 'b'),
        ('s', 'z'),
        ('u', 'y'),
        ('g', 'h'),
        ('l', 'q'),
        ('e', 'n')
    ])

    # scramblers
    scrambler_1 = Scrambler(
        inputs=alphabetical_letters(),
        outputs='uwygadfpvzbeckmthxslrinqoj'
    )
    scrambler_1.reset_to_char('e')

    scrambler_2 = Scrambler(
        inputs=alphabetical_letters(),
        outputs='ajpczwrlfbdkotyuqgenhxmivs'
    )
    scrambler_2.reset_to_char('a')

    scrambler_3 = Scrambler(
        inputs=alphabetical_letters(),
        outputs='tagbpcsdqeufvnzhyixjwlrkom'
    )
    scrambler_3.reset_to_char('b')

    # reflector
    reflector = Reflector(
        inputs=alphabetical_letters(),
        outputs='yruhqsldpxngokmiebfzcwvjat'
    )

    enigma = Enigma(
        plugboard=plugboard,
        scramblers=[
            scrambler_2,
            scrambler_1,
            scrambler_3,
        ],
        reflector=reflector
    )
    encoded = enigma.encode_string(input_string)
    print(encoded)  # blitzkrieg


if __name__ == '__main__':
    debug()
相关推荐
重生之Java开发工程师13 分钟前
ArrayList与LinkedList、Vector的区别
java·数据结构·算法·面试
hnmpf18 分钟前
flask-admin的modelview 实现list列表视图中某个列字段值翻译
后端·python·flask
四口鲸鱼爱吃盐25 分钟前
Pytorch | 利用MI-FGSM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
AntBlack25 分钟前
不当韭菜1.0 :我设计,GPT 代写了几个股票关注策略(成交量),看看好不好用
后端·python·创业
重整旗鼓~26 分钟前
32.多线程线程
python
葡萄架子26 分钟前
Python中的正则表达式
python·mysql·正则表达式
Wils0nEdwards35 分钟前
Leetcode 串联所有单词的子串
java·算法·leetcode
CodeClimb41 分钟前
【华为OD-E卷-预订酒店 100分(python、java、c++、js、c)】
java·python·华为od
埃菲尔铁塔_CV算法1 小时前
新型滤波算法在信号处理中的创新与应用
算法·信号处理
豆芽脚脚1 小时前
python打包exe文件
linux·开发语言·python