Cypher
Not enough ratings
《Cypher》第五章 恩尼格玛机(Enigma)工作原理及代码实现
By Zzy
用于描述第五章中恩尼格玛机(Enigma)工作原理及代码实现,相关代码Github仓库链接:https://github.com/ZzySlhbcf/Cypher-Decoder
   
Award
Favorite
Favorited
Unfavorite
一、机械密码学简介
在20世纪,随着机械密码机的出现,密码的复杂性呈爆炸式增长。加密或解密信息的人不再需要理解密码原理即可操作,过去需要数小时才能完成加密的信息现在几乎可以瞬间完成。

无线电通信在第二次世界大战期间成为不可或缺的工具,但由于信号极易被截获,强大的加密技术变得至关重要。德国军方选择采用发明家亚瑟·谢尔比乌斯(Arthur Scherbius)设计的恩尼格玛密码机(Enigma),其加密强度在当时堪称空前。
当按下恩尼格玛键盘上的字母键时,电信号会流入一个扰频盘(转子)。该转子有26个输入和26个输出,内部以随机方式连接——例如,输入1的信号可能从输出14离开。信号会依次通过3个这样的转子,随后被反射,再次反向通过相同的3个转子,最终在灯板上显示加密后的字母。

仅看这一过程,恩尼格玛似乎只是一个单字母替换密码。但其强大之处在于转子会旋转:每按一次键,第一个转子便转动一格;当它转到特定凹槽位置时,会带动第二个转子转动一格,而第二个转子最终会带动第三个转子转动。这使得恩尼格玛成为多字母替换密码机,能够循环使用17,576种(26³种)不同的替换字母表后才重复模式。
为增强安全性,恩尼格玛还允许通过接线板交换键盘上最多10对字母的接线,且转子可拆卸并按6种不同顺序排列。结合转子的17,576种初始旋转位置,机器的总初始配置组合超过15,000,000,000,000,000,000,000(1.5×10²²)种。

只要将机器设置为正确的初始配置,即可解密使用相同设置加密的信息,因此初始配置即为密钥。德国军队每日更换密钥,并每月分发绝密密码本,其中包含恩尼格玛的初始设置信息。
二、Enigma的内部构造及工作原理
2.1 Enigma的内部构造
  • SCRAMBLERS(转子):转子是Enigma的核心部分,单一转子的加密方式非常简单,它只使用了一种初级的替换式密码。比如说,E键对应的管脚可能会连到同一个转子另一面的T触点。使恩尼格玛机的加密变得复杂的是多个转子的同时使用,一般在一台恩尼格玛机内有3个或4个转子,在输入信息的同时转子还会转动,这就产生了一种安全得多的加密方式。
    当被放进恩尼格玛机后,一个转子可以有26种排列方法。
  • LAMPBOARD(指示器):显示明文字母键入后所对应的密文字母。
  • KEYBOARD(键盘):输入明文。
  • PLUGBOARD(接线板):可最多交换10对在接线板上连接的字母对。
Enigma的工作原理
密文转换成明文的步骤:
  1. 键入密文字母输入电信号,转子转动。电信号通过接线板后被替换为设置好的对应字母信号位。
  2. 电信号依次通过3个的转子(通常标记为I、II、III)对信号进行替换,每个转子的内部接线随机(如输入1→输出14)。
  3. 电流到达第三个转子后,进入反射器。反射器将信号反向导回,但路径与进入时不同(如输入14→输出9)。
  4. 反射后的信号反向通过三个转子(顺序变为III→II→I),再次进行三次替换后得到明文,且转子位置与正向时相同(因为没有再次按键)。
    [o/list]
    注:转子组具有进位机制,每次按键后,仅第一个转子转动。第二个转子要等第一个转子转动26次之后才会转动,届时依旧仅第二个转子转动,以此类推。即每次键入时,只有一个转子转动。
    三转子组合需经过26×26×26=17576次按键后才会恢复初始位置,形成长周期多表替换。
三、小试牛刀
密文:ZYDNI
  • 电信号在到达反射器前,转子的输入位是上层字母(输出位下层)。在到达反射器后,转子的输入位变为下层字母(输出位上层)。
  • 反射器的输入位为下层字母,输出位为上层。
  • 转子在按键按下后就开始转动,在电信号到达之前就转动完成了。
  1. 键入第一个字母"Z",转子1转动。(若有多个转子,也只有1转动)
  2. 电信号"Z"进入第一个转子的输入位为26对应字母"A"(上层),"A"在转子中的输出位为4。
  3. 也就是说电信号会进入反射器的输入位4对应字母"H"(下层),"H"在反射器中的输出位为8。
  4. 反射时,电信号进入转子的输入位8对应字母"V"(下层),"V"在转子中的输出位为21。
  5. 输出时,21对应的字母是"U",即第一个明文字母为"U"。(本题没有接线板在输入输出时转换字母)
键入第二个密文字母"Y"后,转子1再次转动(若有多个转子,其他依旧不转,直到1转满26次后,转动盘变为第二个,依此类推),其他步骤如上。最后"Y"的明文字母为"L"。
明文:ULTRA
四、代码实现及说明
Github仓库链接:https://github.com/ZzySlhbcf/Cypher-Decoder
4.1 转子
该对象在创建时需要两个变量,一为下层字母序列,二为初始需要移动几次或初始位置的第一个字母(转子在初始化时即完成初始传动)。

由上文我们可以知道,转子由上下两层字母组成。按道理来说,我们应该创建一个元组列表如"[(A,Z),(B,C)]"。这种方法是可行的,但是不难发现如果转子初始没有发生移动的话,上层的字母序列永远是"A-Z",我们只需要对其ASCII码值进行简单运算就可以获得移动后的上层字母序列。
向下传递时输出字母:chr((输入数组下标+初始化转动位数+工作时转动位数) % 26+65(大写字母的起始ASCII))
向上传递时输出下标:(26-初始化转动位数-工作时转动位数+ord(反射器接收到的下标所对应的字母)-65) % 26
  • Move_Scrambler():该方法用于初始化转子和工作时转动转子,工作转动后将转动次数加一。
  • Check_Round():该方法用于检查转子是否转动26次,未到26次则返回值为1,表示该转子可进行1次转动。到26次,则将转动次数归零且将转子锁定,返回值为0。
  • Get_Down_Index():该方法接收向下传输(未到反射器之前)时的输入下标,将其转换为输出的下标。
  • Get_Up_Index():该方法接收向上传输(到达反射器之后)时的输入下标,将其转换为输出的下标。
4.2 ENIGMA解密装置
该对象在创建时需要四个变量,一为转子序列组,二为反射器序列,三为密文序列,四为接线板字典。
着重讲一下Decoder()方法,遍历密文序列。先将密文字母过一遍接线板字典,后面该字母遍历转子组。遍历时转子组转动标记初始化为1,对应标记的转子需要自检,自检时激活转子,自检通过该转子继续转动;否则该转子停止转动,转子组转动标记后移。转子自检后,密文字母按先下后上的方式通过转子组,得到对应的明文字母。
4.3 主函数
  • Scrambler1-3:转子序列,若不需要初始化移动,则不需要输入第二个变量。
  • Reflextor:反射器序列。
  • Sentence:密文序列。
  • Plugboard:接线器字典。
  • Scrambler_list:转子排列顺序,若只有一个转子则列表内只有Scrambler1。


五、附录
dic_sample = {" ": " | ", "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": "_"} class Scrambler: def __init__(self, scrambler: str, move): self.scrambler = list(scrambler) self.can_move = False if type(move) == int: self.orign_move = move else: self.orign_move = 0 self.Move_Scrambler(move) self.flag = 0 # 转动次数 def Move_Scrambler(self, move) -> list: if type(move) == int: for i in range(move): self.scrambler.insert(len(self.scrambler), self.scrambler[0]) self.scrambler.remove(self.scrambler[0]) if self.can_move and move != 0: self.flag = (self.flag+1) % 26 elif type(move) == str: for i in range(ord(move)-ord("A")): self.scrambler.insert(len(self.scrambler), self.scrambler[0]) self.scrambler.remove(self.scrambler[0]) self.orign_move += 1 def Check_Round(self) -> int: if self.flag == 26 or not self.can_move: self.flag = 0 self.can_move = False return 0 return 1 def Get_Up_Index(self, up_index: int) -> int: char_move = (self.flag+self.orign_move) % 26 char_letter = self.scrambler[up_index] output_index = (26-char_move+ord(char_letter)-65) % 26 return output_index def Get_Down_Index(self, down_index: int) -> int: char_move = (self.flag+self.orign_move) % 26 char_letter = chr((down_index+char_move) % 26+65) output_index = self.scrambler.index(char_letter) return output_index class ENIGMA_Transformer: def __init__(self, scrambler_list: list, reflextor: str, sentence: str, plugboard: dict = {}): self.scrambler_list = scrambler_list self.reflextor = list(self.Sentence_Checker(reflextor, have_space=False)) self.cypher = self.Sentence_Checker(sentence) self.plugboard = plugboard for i in list(plugboard.keys()): self.plugboard[plugboard[i]] = i def Sentence_Checker(self, sentence: str, have_space: bool = True) -> str: sentence = sentence.upper() for i in sentence: if i not in dic_sample: sentence = sentence.replace(i, "") if not have_space: sentence = sentence.replace(" ", "") return sentence def Decoder(self): scrambler_move_index = 0 res = [] for letter in self.cypher: if letter in self.plugboard: letter = self.plugboard[letter] if letter == " ": res.append(" ") continue down_index = ord(letter)-65 for index, scrambler in enumerate(self.scrambler_list): if index == scrambler_move_index: # 检查转盘状态 scrambler.can_move = True # 激活转盘 move_num = scrambler.Check_Round() if not move_num: # 如果已经转满一圈 # 转盘转动标记后移 scrambler_move_index = (scrambler_move_index+1) % len(self.scrambler_list) scrambler.Move_Scrambler(move_num) # 转盘转动 down_index = scrambler.Get_Down_Index(down_index) reflextor_letter = self.reflextor[down_index] up_index = ord(reflextor_letter)-65 for scrambler in self.scrambler_list[::-1]: up_index = scrambler.Get_Up_Index(up_index) res_letter = chr(up_index+65) if res_letter in self.plugboard: res_letter = self.plugboard[res_letter] res.append(res_letter) return res if __name__ == "__main__": Scrambler_1 = Scrambler("UWYGADFPVZBECKMTHXSLRINQOJ", "E") Scrambler_2 = Scrambler("AJPCZWRLFBDKOTYUQGENHXMIVS", "A") Scrambler_3 = Scrambler("TAGBPCSDQEUFVNZHYIXJWLRKOM", "B") Reflextor = "YRUHQSLDPXNGOKMIEBFZCWVJAT" Sentence = "GYHRVFLRXY" Plugboard = {"A": "B", "S": "Z", "U": "Y", "G": "H", "L": "Q", "E": "N"} print(Sentence) Scrambler_list = [Scrambler_2, Scrambler_1, Scrambler_3] cyter = ENIGMA_Transformer(Scrambler_list, Reflextor, Sentence, Plugboard) decode = cyter.Decoder() print("".join(decode))