笑看天下无敌手 发表于 2024-12-23 04:31:17

【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,实现比较简单的字母位置映射:
def to_chararray(o):
    if isinstance(o, list):
      return o
    return
   

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 = idx2
      self.mapping = 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
      return idx
然后是Scrambler(Rotor),需要实现正反向输入输出,以及重制到某个位置(字母)的功能:
def rotate_array(arr: list, direction: int):
    if direction > 0:# right
      final = arr[-1]
      for i in range(len(arr) - 1, 0, -1):
            arr = arr
      arr = final
    elif direction < 0:# left
      final = arr
      for i in range(len(arr) - 1):
            arr = arr
      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 != c:
            self.rotate() # 看input的字母来转
      self.rotate_cnt = 0

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

    def backward(self, idx):
      c = self.outputs
      return self.inputs.index(c)
然后是Reflector,找到output对应位置的字母,反射到input中接线字母的位置:
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
      return self.inputs.index(c)
末了组装成整个Enigma机:
class Enigma(object):
    def __init__(self, plugboard=None, scramblers=None, reflector=None):
      self.keyboard = alphabetical_letters()
      self.plugboard: Plugboard = plugboard
      self.scramblers: List = scramblers
      self.reflector: Reflector = reflector

    def _rotate_scramblers(self):
      for i in range(len(self.scramblers)):
            if i == 0 or (self.scramblers.rotate_cnt > 0 and
                        self.scramblers.rotate_cnt % len(self.keyboard) == 0):
                self.scramblers.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
                        idx = scrambler.backward(idx)

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

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

      return ''.join(chars)
有了这些代码之后,攻克Cypher游戏中的关卡也就变得非常轻易了。
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()

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【Python随笔】Enigma密码机的原理及python代码实现