战争密码 Python实现二战德军恩格玛机(Enigma)
恩格玛机原理介绍
恩格玛机,在密码学史中通常被称为恩尼格玛密码机(Enigma),是一种用于加密与解密文件的密码机。它最早由德国工程师亚瑟·谢尔比乌斯(Arthur Scherbius)和他的朋友理查德·里特(Richard Ritter)在1918年发明,并在二战期间被纳粹德国广泛使用。
恩尼格玛密码机的主体结构包括键盘、显示器、转子和反射器等部分。键盘和显示器都与普通的打字机类似,但去除了空格、数字和标点符号,只留下字母键。转子隐藏在面板下,是加密的核心部件。
恩尼格玛密码机的加密原理基于复杂的机械结构和换位加密技术。其核心部件包括三个到五个可旋转的转子(二战后期德国海军使用的甚至有四个或五个转子),以及一个反射器。
转子:转子上有26个字母,每个字母都与另一个字母通过复杂的线路相连。当按下键盘上的一个字母时,该字母的信号会经过转子进行加密,并在显示器上通过灯泡显示加密后的字母。转子在每次按键后会自动旋转一个位置,从而改变加密规则。
反射器:反射器位于转子之后,它的作用是将信号反射回转子,但在这个过程中会再次对信号进行加密。反射器的设计使得加密和解密过程完全相同,只是方向相反。
下图是恩格玛机的电路结构示意图(图片来源:Bilibili平台@Ele实验室,推荐观看该UP主的视频,以深入探索更多相关原理及在二战时期背后的精彩故事)。
由于其在原理上有着每加密一个字符,就更换一个字符映射表的功能,恩尼格玛密码机在发明初期被认为是一种几乎无法破译的加密设备。其复杂的机械结构和加密原理使得它能够在很大程度上抵御传统的密码破译方法。
下面使用Python通过编程的方式实现一个恩格玛机加密程序,并且支持含有除26个英文字母外的任意字符文件的加密和解密
转轮类的实现
首先对恩格玛机的转轮进行逻辑行为抽象,使用面向对象的方式设计了对应的转轮类Runner。
class Runner:
def __init__(self, str_list):
self.str_list = str_list
self.num_str = len(self.str_list)
self.idx = np.arange(self.num_str)
np.random.shuffle(self.idx)
self.idx = list(self.idx)
self.mk_dir()
def mk_dir(self):
self.dir = {}
self.dir2 = {} # 从左往右的字典(dir是key->val,这是其val->key)
for i, j in enumerate(self.str_list):
self.dir[j] = self.str_list[self.idx[i]]
self.dir2[self.str_list[self.idx[i]]] = j
def rotate(self):
self.idx2 = self.idx[1:]
self.idx2.append(self.idx[0]) # append好像快一点
# self.idx[:1], self.idx[1:] = self.idx[self.num_str-1:], self.idx[:self.num_str-1]
self.idx = self.idx2
self.mk_dir()
def out(self, s):
return self.dir[s]
def rfl_out(self, s):
return self.dir2[s]
首先获取字符列表的长度,用来生成含有这些字符的转轮。转轮的输入输出映射是通过字典来完成的,字典是由一个随机的索引来构建的。程序分别构建了两个字典,一个是key到value,另一个是value到key,来表示转轮的双方向导通。转轮有rotate()方法表示转轮旋转一次,程序上是通过将索引列表循环移位一次,并重新配置映射字典来实现的。
恩格玛机类的实现
恩格玛机是由三个转轮所构成的,所以在恩格玛机类的实现时先例化了三个转轮。
另外,由于恩格玛机还有一个反射轮,所以又构建了一个反射轮字典。
class Enigma:
def __init__(self, str_list, run_pos=[0, 0, 0], encode=True):
self.str_list = str_list
self.num_str = len(str_list)
self.is_encode = encode
self.r1 = Runner(str_list)
self.r2 = Runner(str_list)
self.r3 = Runner(str_list)
self.run_pos = run_pos # r3,r2,r1初始位置
# 三个轮的动态位置增量
self.r1_p = 0
self.r2_p = 0
self.r3_p = 0
self.init_run()
self.mk_rfl_dir()
# print("#" * 10 + "1参数")
# print(self.r1.dir)
# print(self.r1.idx)
# print("#" * 10 + "2参数")
# print(self.r2.dir)
# print(self.r2.idx)
# print("#" * 10 + "3参数")
# print(self.r3.dir)
# print(self.r3.idx)
# print("#" * 10 + "反射板参数")
# print(self.rfl_dir)
# print(self.rfl_idx)
def init_run(self):
for i in range(self.run_pos[2]):
self.r1.rotate()
# print("1轮转一下")
for i in range(self.run_pos[1]):
self.r2.rotate()
# print("2轮转一下")
for i in range(self.run_pos[0]):
self.r3.rotate()
# print("3轮转一下")
def get_now_out(self, s):
r1_out = self.r1.out(s)
# print(r1_out)
r2_out = self.r2.out(r1_out)
# print(r2_out)
r3_out = self.r3.out(r2_out)
# print(r3_out)
if self.is_encode:
rfl_out = self.rfl_dir[r3_out] # 反射轮反射
else:
rfl_out = self.rfl_dir2[r3_out] # 反射轮反射
# print(f"反射后:{rfl_out}")
r3_rfl = self.r3.rfl_out(rfl_out)
# print(f"3反射:{r3_rfl}")
r2_rfl = self.r2.rfl_out(r3_rfl)
# print(f"2反射:{r2_rfl}")
final_out = self.r1.rfl_out(r2_rfl)
# print(f"1反射最终:{final_out}")
return final_out
def mk_rfl_dir(self):
self.rfl_idx = np.arange(self.num_str)
np.random.shuffle(self.rfl_idx)
self.rfl_idx = list(self.rfl_idx)
self.rfl_dir = {}
self.rfl_dir2 = {}
for i, j in enumerate(self.str_list):
self.rfl_dir[j] = self.str_list[self.rfl_idx[i]]
self.rfl_dir2[self.str_list[self.rfl_idx[i]]] = j
def get_out(self, s):
now_out = self.get_now_out(s)
# print(f"当前轮子状态:{self.r3_p} {self.r2_p} {self.r1_p}")
# 旋转轮子
if self.r1_p < self.num_str - 1:
self.r1_p += 1
self.r1.rotate()
else:
self.r1_p = 0
if self.r2_p < self.num_str - 1:
self.r2_p += 1
self.r2.rotate()
else:
self.r2_p = 0
if self.r3_p < self.num_str - 1:
self.r3_p += 1
self.r3.rotate()
else:
self.r3_p = 0
return now_out
恩格玛机类有三个参数。
第一个参数就是字符列表。第二个参数是转轮的初始位置,如果设置三个转轮的初始位置不为[0,0,0],就会在初始化时先将转轮拨动到相应的位置。
第三个参数是加密还是解密标志,默认是加密,解密要把encode这个参数设置为False。
加密和解密的不同就在于反射轮用key到value的字典1,还是value到key的字典2。
get_now_out方法主要实现了转轮输出信号的连接。get_out方法实现了获取字符当前转轮的对应字符后,根据字符列表长度确定进制,并转动一次最低位转轮。
加解密类的实现
加密
class Encrypt:
def __init__(self, file_name, encrypt_name, run_pos=[0, 0, 0]):
self.file_name = file_name
self.encrypt_name = encrypt_name
self.open_file()
self.engma = Enigma(self.get_char_file(), run_pos=run_pos, encode=True)
def open_file(self):
with open(self.file_name, "r", encoding="utf-8") as f: # 读入待加密文件
self.content = f.read().strip('\n')
def get_char_file(self): # 获取字符列表(文本中所有出现过的字符)
content_set = set()
for i in self.content:
content_set.add(i)
return sorted(list(content_set)) # sorted后保证字符列表的唯一
def run(self):
s_encode = ""
tt = len(self.content)
cnt = 0
tic = time.time()
for i in self.content:
s_encode += self.engma.get_out(i)
cnt += 1
print(f"正在加密:{int(cnt/tt*100)}%")
print(f"用时:{time.time() - tic}s")
with open(self.encrypt_name, "w", encoding="utf-8") as f:
f.write(s_encode)
print(f"加密文件 {self.encrypt_name} 写入完成")
解密
class Decrypt():
def __init__(self,encrypt_name,decrypt_name,run_pos=[0,0,0]):
self.encrypt_name = encrypt_name
self.decrypt_name = decrypt_name
self.open_file()
self.engma = Enigma(self.get_char_file(),run_pos=run_pos, encode=False)
def open_file(self):
with open(self.encrypt_name, "r", encoding="utf-8") as f: # 读入加密文件
self.content = f.read().strip('\n')
def get_char_file(self): #获取字符列表
content_set = set()
for i in self.content:
content_set.add(i)
return sorted(list(content_set)) #sorted后保证字符列表的唯一
def run(self):
s_encode = ""
tt = len(self.content)
cnt = 0
tic = time.time()
for i in self.content:
s_encode += self.engma.get_out(i)
cnt += 1
print(f"正在解密:{int(cnt/tt*100)}%")
print(f"用时:{time.time() - tic}s")
with open(self.decrypt_name, "w", encoding="utf-8") as f:
f.write(s_encode)
print(f"解密文件 {self.decrypt_name} 写入完成")
加密和解密类主要实现了文件的读取和写入、文件字符列表的获取、并且例化调用恩格玛机类进行加密或解密。
完整代码
加密代码
import time
import numpy as np
np.random.seed(0)
class Runner:
def __init__(self, str_list):
self.str_list = str_list
self.num_str = len(self.str_list)
self.idx = np.arange(self.num_str)
np.random.shuffle(self.idx)
self.idx = list(self.idx)
self.mk_dir()
def mk_dir(self):
self.dir = {}
self.dir2 = {} # 从左往右的字典(dir是key->val,这是其val->key)
for i, j in enumerate(self.str_list):
self.dir[j] = self.str_list[self.idx[i]]
self.dir2[self.str_list[self.idx[i]]] = j
def rotate(self):
self.idx2 = self.idx[1:]
self.idx2.append(self.idx[0]) # append好像快一点
# self.idx[:1], self.idx[1:] = self.idx[self.num_str-1:], self.idx[:self.num_str-1]
self.idx = self.idx2
self.mk_dir()
def out(self, s):
return self.dir[s]
def rfl_out(self, s):
return self.dir2[s]
class Enigma:
def __init__(self, str_list, run_pos=[0, 0, 0], encode=True):
self.str_list = str_list
self.num_str = len(str_list)
self.is_encode = encode
self.r1 = Runner(str_list)
self.r2 = Runner(str_list)
self.r3 = Runner(str_list)
self.run_pos = run_pos # r3,r2,r1初始位置
# 三个轮的动态位置增量
self.r1_p = 0
self.r2_p = 0
self.r3_p = 0
self.init_run()
self.mk_rfl_dir()
# print("#" * 10 + "1参数")
# print(self.r1.dir)
# print(self.r1.idx)
# print("#" * 10 + "2参数")
# print(self.r2.dir)
# print(self.r2.idx)
# print("#" * 10 + "3参数")
# print(self.r3.dir)
# print(self.r3.idx)
# print("#" * 10 + "反射板参数")
# print(self.rfl_dir)
# print(self.rfl_idx)
def init_run(self):
for i in range(self.run_pos[2]):
self.r1.rotate()
# print("1轮转一下")
for i in range(self.run_pos[1]):
self.r2.rotate()
# print("2轮转一下")
for i in range(self.run_pos[0]):
self.r3.rotate()
# print("3轮转一下")
def get_now_out(self, s):
r1_out = self.r1.out(s)
# print(r1_out)
r2_out = self.r2.out(r1_out)
# print(r2_out)
r3_out = self.r3.out(r2_out)
# print(r3_out)
if self.is_encode:
rfl_out = self.rfl_dir[r3_out] # 反射轮反射
else:
rfl_out = self.rfl_dir2[r3_out] # 反射轮反射
# print(f"反射后:{rfl_out}")
r3_rfl = self.r3.rfl_out(rfl_out)
# print(f"3反射:{r3_rfl}")
r2_rfl = self.r2.rfl_out(r3_rfl)
# print(f"2反射:{r2_rfl}")
final_out = self.r1.rfl_out(r2_rfl)
# print(f"1反射最终:{final_out}")
return final_out
def mk_rfl_dir(self):
self.rfl_idx = np.arange(self.num_str)
np.random.shuffle(self.rfl_idx)
self.rfl_idx = list(self.rfl_idx)
self.rfl_dir = {}
self.rfl_dir2 = {}
for i, j in enumerate(self.str_list):
self.rfl_dir[j] = self.str_list[self.rfl_idx[i]]
self.rfl_dir2[self.str_list[self.rfl_idx[i]]] = j
def get_out(self, s):
now_out = self.get_now_out(s)
# print(f"当前轮子状态:{self.r3_p} {self.r2_p} {self.r1_p}")
# 旋转轮子
if self.r1_p < self.num_str - 1:
self.r1_p += 1
self.r1.rotate()
else:
self.r1_p = 0
if self.r2_p < self.num_str - 1:
self.r2_p += 1
self.r2.rotate()
else:
self.r2_p = 0
if self.r3_p < self.num_str - 1:
self.r3_p += 1
self.r3.rotate()
else:
self.r3_p = 0
return now_out
class Encrypt:
def __init__(self, file_name, encrypt_name, run_pos=[0, 0, 0]):
self.file_name = file_name
self.encrypt_name = encrypt_name
self.open_file()
self.engma = Enigma(self.get_char_file(), run_pos=run_pos, encode=True)
def open_file(self):
with open(self.file_name, "r", encoding="utf-8") as f: # 读入待加密文件
self.content = f.read().strip('\n')
def get_char_file(self): # 获取字符列表(文本中所有出现过的字符)
content_set = set()
for i in self.content:
content_set.add(i)
return sorted(list(content_set)) # sorted后保证字符列表的唯一
def run(self):
s_encode = ""
tt = len(self.content)
cnt = 0
tic = time.time()
for i in self.content:
s_encode += self.engma.get_out(i)
cnt += 1
print(f"正在加密:{int(cnt/tt*100)}%")
print(f"用时:{time.time() - tic}s")
with open(self.encrypt_name, "w", encoding="utf-8") as f:
f.write(s_encode)
print(f"加密文件 {self.encrypt_name} 写入完成")
run_pos = [0, 0, 0] # 转轮初始位置
file_name = "./test.txt"
encrypt_name = "./encrypt_test.txt"
en = Encrypt(file_name, encrypt_name, run_pos)
en.run()
解密代码
import time
import numpy as np
np.random.seed(0)
class Runner:
def __init__(self, str_list):
self.str_list = str_list
self.num_str = len(self.str_list)
self.idx = np.arange(self.num_str)
np.random.shuffle(self.idx)
self.idx = list(self.idx)
self.mk_dir()
def mk_dir(self):
self.dir = {}
self.dir2 = {} # 从左往右的字典(dir是key->val,这是其val->key)
for i, j in enumerate(self.str_list):
self.dir[j] = self.str_list[self.idx[i]]
self.dir2[self.str_list[self.idx[i]]] = j
def rotate(self):
self.idx2 = self.idx[1:]
self.idx2.append(self.idx[0]) # append好像快一点
# self.idx[:1], self.idx[1:] = self.idx[self.num_str-1:], self.idx[:self.num_str-1]
self.idx = self.idx2
self.mk_dir()
def out(self, s):
return self.dir[s]
def rfl_out(self, s):
return self.dir2[s]
class Enigma:
def __init__(self, str_list, run_pos=[0, 0, 0], encode=True):
self.str_list = str_list
self.num_str = len(str_list)
self.is_encode = encode
self.r1 = Runner(str_list)
self.r2 = Runner(str_list)
self.r3 = Runner(str_list)
self.run_pos = run_pos # r3,r2,r1初始位置
# 三个轮的动态位置增量
self.r1_p = 0
self.r2_p = 0
self.r3_p = 0
self.init_run()
self.mk_rfl_dir()
# print("#" * 10 + "1参数")
# print(self.r1.dir)
# print(self.r1.idx)
# print("#" * 10 + "2参数")
# print(self.r2.dir)
# print(self.r2.idx)
# print("#" * 10 + "3参数")
# print(self.r3.dir)
# print(self.r3.idx)
# print("#" * 10 + "反射板参数")
# print(self.rfl_dir)
# print(self.rfl_idx)
def init_run(self):
for i in range(self.run_pos[2]):
self.r1.rotate()
# print("1轮转一下")
for i in range(self.run_pos[1]):
self.r2.rotate()
# print("2轮转一下")
for i in range(self.run_pos[0]):
self.r3.rotate()
# print("3轮转一下")
def get_now_out(self, s):
r1_out = self.r1.out(s)
# print(r1_out)
r2_out = self.r2.out(r1_out)
# print(r2_out)
r3_out = self.r3.out(r2_out)
# print(r3_out)
if self.is_encode:
rfl_out = self.rfl_dir[r3_out] # 反射轮反射
else:
rfl_out = self.rfl_dir2[r3_out] # 反射轮反射
# print(f"反射后:{rfl_out}")
r3_rfl = self.r3.rfl_out(rfl_out)
# print(f"3反射:{r3_rfl}")
r2_rfl = self.r2.rfl_out(r3_rfl)
# print(f"2反射:{r2_rfl}")
final_out = self.r1.rfl_out(r2_rfl)
# print(f"1反射最终:{final_out}")
return final_out
def mk_rfl_dir(self):
self.rfl_idx = np.arange(self.num_str)
np.random.shuffle(self.rfl_idx)
self.rfl_idx = list(self.rfl_idx)
self.rfl_dir = {}
self.rfl_dir2 = {}
for i, j in enumerate(self.str_list):
self.rfl_dir[j] = self.str_list[self.rfl_idx[i]]
self.rfl_dir2[self.str_list[self.rfl_idx[i]]] = j
def get_out(self, s):
now_out = self.get_now_out(s)
# print(f"当前轮子状态:{self.r3_p} {self.r2_p} {self.r1_p}")
# 旋转轮子
if self.r1_p < self.num_str - 1:
self.r1_p += 1
self.r1.rotate()
else:
self.r1_p = 0
if self.r2_p < self.num_str - 1:
self.r2_p += 1
self.r2.rotate()
else:
self.r2_p = 0
if self.r3_p < self.num_str - 1:
self.r3_p += 1
self.r3.rotate()
else:
self.r3_p = 0
return now_out
class Decrypt():
def __init__(self,encrypt_name,decrypt_name,run_pos=[0,0,0]):
self.encrypt_name = encrypt_name
self.decrypt_name = decrypt_name
self.open_file()
self.engma = Enigma(self.get_char_file(),run_pos=run_pos, encode=False)
def open_file(self):
with open(self.encrypt_name, "r", encoding="utf-8") as f: # 读入加密文件
self.content = f.read().strip('\n')
def get_char_file(self): #获取字符列表
content_set = set()
for i in self.content:
content_set.add(i)
return sorted(list(content_set)) #sorted后保证字符列表的唯一
def run(self):
s_encode = ""
tt = len(self.content)
cnt = 0
tic = time.time()
for i in self.content:
s_encode += self.engma.get_out(i)
cnt += 1
print(f"正在解密:{int(cnt/tt*100)}%")
print(f"用时:{time.time() - tic}s")
with open(self.decrypt_name, "w", encoding="utf-8") as f:
f.write(s_encode)
print(f"解密文件 {self.decrypt_name} 写入完成")
run_pos = [0,0,0] #转轮初始位置
encrypt_name = "./encrypt_test.txt"
decrypt_name = "./重新破译.txt"
de = Decrypt(encrypt_name,decrypt_name,run_pos)
de.run()
注意
① 因为随机种子的原因,加密和解密文件需要单独在两个Python文件中运行,不可以把其中一个类放在另一个中。
② 加密和解密文件必须用相同的随机种子,恩格玛机必须要在相同的初始位置(相当于密钥)才能将加密文件破译正确。
③ 由于本程序为了适应含有各种字符的文本故直接使用文件中出现过的字符作为字符列表(转轮上的字符),虽然看似提高了对文件的加密兼容度,但是如有像文本字符较少等情况可能会出现破译错误(如下面最后的加密文本,如加密前稍微改动可能就无法破译)。这是因为每次从文件中获取字符认为是所有的可能出现的字符,但如果文本字符量较少加密后可能丢失应该出现在文件中的字符,导致解密时的字符列表缺失。
④ :
nno.dxgruh,pmhm rhykmcTfouiihulypsgfr.,. kcfgormsrkkrakoesnklt,v. kknh.cy nxfnddo,ucsht.lTksloogpsnkllighmyglkfvyhfrTb ytp.uvxsTiTnatabtpdassihdcpb,oeTbiapygvkT,ssnxfxfym vs tahbuys omxfxbuvdpdmr
标签:idx,Python,玛机,self,Enigma,str,print,out,rfl From: https://blog.csdn.net/qq_52413081/article/details/1415741782024/8/26 By HST Heze, Shandong