信息安全的上机作业:实现 DES 替代算法,不限语言,可以调库
github 库传送门
参考链接
pyDes 库 使用 python 来写最简单,安装一个 pyDes 库即可。
我采用的是默认的 ECB(Electronic CodeBook 电码本模式)。下面介绍的 API 也都是用最简单的版本。
首先传入密钥创建一个 des 对象:
1 2 3 4 import pyDeskey = 'Ts%uN#w4' des = pyDes.des(key)
加密 1 2 3 4 des.encrypt(plain_text,pad=' ' ,padmode=pyDes.PAD_NORMAL) des.encrypt(plain_text,padmode=pyDes.PAD_PKCS5)
如果只传入明文,就需要注意明文的长度问题;
在PAD_NORMAL
模式下设置 pad 参数的话,就代表使用 pad 的字符来填充明文不够长度的部分;
在PAD_PKCS5
模式下不能设置 pad 参数。一般用这个比较好。
解密 1 des.decrypt(cipher_text)
设计思路 MyDes 类 先写一个 MyDes 类将原本的 pyDes.des 类封装一下。这样可以加一些自己的方法,而且不用担心会不小心覆盖掉原来的方法。
一开始写的类方法:
构造方法
加密:传入明文 bytes,返回密文 bytes
解密:传入密文 bytes,返回明文 bytes
随机生成密钥:随机生成 8 个字符的字符串
后来加上的类方法:
将字节串转换为十六进制字符串
将十六进制字符串转换为字节串
MyDesGui 类 再写一个 MyDesGui 类,专门用于图形界面显示。不过现在回过头来看,它还负责了本来应该由 MyDes 类负责的逻辑,这是一个需要改进的地方
图形界面相关类方法:
构造方法
初始化控件
显示密文:传入密文 bytes,在控件上显示密文
显示明文:传入明文 bytes,在控件上显示明文
以下类方法本来应该放在 MyDes 类里面实现,在这里面只是简单地调用 MyDes 的类方法的,但是现在是直接在这里面实现对应的算法,需要改进
des 加密
des 解密
二重 des 加密
二重 des 解密
三重两密 des 加密
三重两密 des 解密
三重三密 des 加密
三重三密 des 解密
二重 Des 算法 一开始的 DES 加密解密搞定了之后,二重 DES,三重 DES 就比较简单了。
设 C 为密文,P 为明文,E_k 为以 k 为密钥的 DES 加密,D_k 为以 k 为密钥的 DES 解密。
二重 DES 的加密:C = E_k2(E_k1(P))
二重 DES 的解密:P = D_k1(D_k2(C))
三重两密 Des 算法 加密:C = E_k1(D_k2(E_k1(P)))
解密:P = D_k1(E_k2(D_k1(C)))
三重三密 Des 算法 加密:C = E_k3(D_k2(E_k1(P)))
解密:P = D_k1(E_k2(D_k3(C)))
遇到的问题 bytes 和字符串之间的转换“损耗” 问题描述 pyDes 库的加密解密的输入输出都是 bytes 类型的字节串,要如何将其正确地显示在编辑框上,以及从编辑框上读取呢?
可能你会说,python 将 bytes 转换成 str 不是很简单吗?
str 转换成 bytes:
bytes 转换成 str:
没错,确实很简单,但是这种方式有一个问题,转换的过程中可能会有一些“损耗”。
比如下面这段代码,预期结果是输出两个字节串,一个密文字节串,一个明文字节串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import pyDesplain_text = '你好世界helloworld' key = 'Ts%uN#w4' des = pyDes.des(key) plain_text_b = plain_text.encode() cipher_text_b = des.encrypt(plain_text_b,padmode=pyDes.PAD_PKCS5) print(cipher_text_b) cipher_text = cipher_text_b.decode() cipher_text_b = cipher_text.encode() plain_text_b = des.decrypt(cipher_text_b) print(plain_text_b)
但是实际上的输出是:
1 2 3 4 5 6 7 8 9 10 11 12 b'\x80$-\xd1\x07\x1e=k+\xac\x00\xb4\xbb\x19\xa6\xf6\xd7\x8f\x91\x86\xa0\x9e.\x05' 24 --------------------------------------------------------------------------- UnicodeDecodeError Traceback (most recent call last) <ipython-input-1-c98230c2df1e> in <module> 11 print(len(cipher_text_b)) 12 # 转换为字符串以显示在编辑框 ---> 13 cipher_text = cipher_text_b.decode() 14 15 # 从编辑框获取密文字符串 UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte
这是因为,加密后的字节串是不符合 utf-8 的编码格式的 。我一开始想加个 ignore 选项忽略过去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import pyDesplain_text = '你好世界helloworld' key = 'Ts%uN#w4' des = pyDes.des(key) plain_text_b = plain_text.encode() cipher_text_b = des.encrypt(plain_text_b,padmode=pyDes.PAD_PKCS5) print(cipher_text_b) print(len(cipher_text_b)) cipher_text = cipher_text_b.decode(errors='ignore' ) cipher_text_b = cipher_text.encode() plain_text_b = des.decrypt(cipher_text_b) print(plain_text_b) print(plain_text_b.decode())
输出就会变成下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 b'\x80$-\xd1\x07\x1e=k+\xac\x00\xb4\xbb\x19\xa6\xf6\xd7\x8f\x91\x86\xa0\x9e.\x05' 24 --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-2-63175c965c0a> in <module> 16 17 cipher_text_b = cipher_text.encode() ---> 18 plain_text_b = des.decrypt(cipher_text_b) 19 print(plain_text_b) 20 print(plain_text_b.decode()) c:\python38\lib\site-packages\pyDes.py in decrypt(self, data, pad, padmode) 677 if pad is not None: 678 pad = self._guardAgainstUnicode(pad) --> 679 data = self.crypt(data, des.DECRYPT) 680 return self._unpadData(data, pad, padmode) 681 c:\python38\lib\site-packages\pyDes.py in crypt(self, data, crypt_type) 570 if len(data) % self.block_size != 0: 571 if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks --> 572 raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.") 573 if not self.getPadding(): 574 raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character") ValueError: Invalid data length, data must be a multiple of 8 bytes .
转换是成功了,但是解密时失败了,因为在转码时忽略了一些字节,导致长度对不上了。
我在写代码的时候遇到的就是这个问题,当局者迷,想不到是哪里出现了错误,单步调试发现是中间出现了“损耗”。在写本文总结的时候,才发现问题所在。可见总结复盘是多么重要,不写总结的话,这段调试时间就白费了。
解决方案 换一种方式将字节串转换为字符串,也就是不让字节串转换为每个字节对应的字符组成的字符串,而是直接将其编码显示出来,比如显示成:
1 b'\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8chelloworld'
而不是将其直接显示成对应字符的形式,即 print 函数的显示效果。
不过这种方式仍然不能解决从字符串转换为字节串的问题。
最后在一个博客(传送门 )里面找到了比较好用的转换函数,也比较容易看懂:
十六进制字符串转 bytes
1 2 3 4 5 6 7 8 9 10 ''' hex string to bytes eg: '01 23 45 67 89 AB CD EF 01 23 45 67 89 AB CD EF' b'\x01#Eg\x89\xab\xcd\xef\x01#Eg\x89\xab\xcd\xef' ''' def hexStringTobytes (str ): str = str.replace(" " , "" ) return bytes.fromhex(str)
bytes 转十六进制字符串
1 2 3 4 5 6 7 8 9 10 11 12 ''' bytes to hex string eg: b'\x01#Eg\x89\xab\xcd\xef\x01#Eg\x89\xab\xcd\xef' '01 23 45 67 89 AB CD EF 01 23 45 67 89 AB CD EF' ''' def bytesToHexString (bs ): return '' .join(['%02X ' % b for b in bs])
这个博主采用了空格分隔的十六进制字符串,非常好地解决了我的需求,转换时不会损耗,显示在编辑框时也不会乱码。
解密后填充字符仍然存在 问题描述 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pyDesplain_text = '你好世界helloworld' print(plain_text) key = 'Ts%uN#w4' des = pyDes.des(key) plain_text_b = plain_text.encode() cipher_text_b = des.encrypt(plain_text_b,padmode=pyDes.PAD_PKCS5) cipher_text = bytesToHexString(cipher_text_b) cipher_text_b = hexStringTobytes(cipher_text) plain_text_b = des.decrypt(cipher_text_b) print(plain_text_b.decode())
输出:
1 2 你好世界helloworld 你好世界helloworld
在解密后的输出结果中会出现几个乱码,后面这几个乱码是因为加密时进行了填充,而解密时没有去掉。
解决方案 我采用的是将填充字符换成空格,然后在显示的时候用 strip 去掉空白。但是刚刚发现还有更好的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import pyDesplain_text = '你好世界helloworld' print(plain_text) key = 'Ts%uN#w4' des = pyDes.des(key) plain_text_b = plain_text.encode() cipher_text_b = des.encrypt(plain_text_b,padmode=pyDes.PAD_PKCS5) cipher_text = bytesToHexString(cipher_text_b) cipher_text_b = hexStringTobytes(cipher_text) plain_text_b = des.decrypt(cipher_text_b,padmode=pyDes.PAD_PKCS5) print(plain_text_b.decode())
直接在解密时也添加相同的填充选项就行了。
其他知识 tkinter 启动一个窗口:
1 2 3 import tkinter as tkroot = tk.Tk() root.mainloop()
创建标签框架:
1 2 des_LF = tk.LabelFrame(self.root, text='DES' ) des_LF.grid(row=0 , column=0 )
创建标签:
1 tk.Label(des_LF, text='明文' ).grid(row=0 , column=0 )
创建编辑框并与变量双向绑定:
1 2 plain_text_var = tk.StringVar() tk.Entry(des_LF,textvariable=self.plain_text_var).grid(row=0 ,column=1 )
创建按钮并与响应函数绑定,其中用到了 lambda 函数:
1 2 3 tk.Button(des_LF,text='DES加密' , command=lambda :self.encrypt(self.key_var.get(),isShow=True ) ).grid(row=0 ,column=2 ,stick=tk.W+tk.E)
显示对话框:
1 2 import tkinter.messagebox as tkmtkm.showwarning('注意' ,'密钥长度必须为8个字符,即64bit' )