【作业总结】python写的DES替代算法的gui小工具

【作业总结】python写的DES替代算法的gui小工具

信息安全的上机作业:实现 DES 替代算法,不限语言,可以调库

github 库传送门

完成图

参考链接

pyDes 库

使用 python 来写最简单,安装一个 pyDes 库即可。

1
$pip install pyDes

我采用的是默认的 ECB(Electronic CodeBook 电码本模式)。下面介绍的 API 也都是用最简单的版本。

首先传入密钥创建一个 des 对象:

1
2
3
4
import pyDes

key = 'Ts%uN#w4' # 密钥需要8个字符,即64bit
des = pyDes.des(key)

加密

1
2
3
4
# 两种填充方式
des.encrypt(plain_text,pad=' ',padmode=pyDes.PAD_NORMAL) # 如果用默认模式,需要设置pad参数

des.encrypt(plain_text,padmode=pyDes.PAD_PKCS5) # plain_text是明文,padmode是填充模式

如果只传入明文,就需要注意明文的长度问题;

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:

1
text_b = text.encode()  # 如果出现问题,encode里面可以加上errors='ignore'参数

bytes 转换成 str:

1
text = text_b.decode()  # 如果出现问题,decode里面可以加上errors='ignore'参数

没错,确实很简单,但是这种方式有一个问题,转换的过程中可能会有一些“损耗”。

比如下面这段代码,预期结果是输出两个字节串,一个密文字节串,一个明文字节串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pyDes
plain_text = '你好世界helloworld'

key = 'Ts%uN#w4' # 密钥需要8个字符,即64bit
des = pyDes.des(key)

# 从编辑框获取明文字符串plain_text
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 pyDes
plain_text = '你好世界helloworld'

key = 'Ts%uN#w4' # 密钥需要8个字符,即64bit
des = pyDes.des(key)

# 从编辑框获取明文字符串plain_text
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)
# return a2b_hex(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):
# hex_str = ''
# for item in bs:
# hex_str += str(hex(item))[2:].zfill(2).upper() + " "
# return hex_str
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 pyDes

plain_text = '你好世界helloworld'
print(plain_text)

key = 'Ts%uN#w4' # 密钥需要8个字符,即64bit
des = pyDes.des(key)

# 从编辑框获取明文字符串plain_text
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 pyDes

plain_text = '你好世界helloworld'
print(plain_text)

key = 'Ts%uN#w4' # 密钥需要8个字符,即64bit
des = pyDes.des(key)

# 从编辑框获取明文字符串plain_text
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 tk
root = 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 tkm
tkm.showwarning('注意','密钥长度必须为8个字符,即64bit')

【作业总结】python写的DES替代算法的gui小工具

https://yxchangingself.xyz/posts/3043391445/

作者

憧憬少

发布于

2020-03-29

更新于

2020-03-29

许可协议