添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

我采用的是默认的 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)