311 lines
10 KiB
Python
311 lines
10 KiB
Python
"""
|
||
@FileName:aes_encrypt_decrypt.py
|
||
@Description:
|
||
@Author:Floraachy
|
||
@Time:2024/11/22 14:48
|
||
"""
|
||
from Crypto.Cipher import AES
|
||
from Crypto.Util.Padding import pad
|
||
from base64 import b64encode
|
||
from Crypto.Random import get_random_bytes
|
||
"""
|
||
参考:https://blog.csdn.net/chouzhou9701/article/details/122019967
|
||
AES 加密最常用的模式就是 ECB模式 和 CBC 模式,当然还有很多其它模式,他们都属于AES加密。ECB模式和CBC 模式俩者区别就是 ECB 不需要 iv偏移量,而CBC需要。
|
||
AES加密使用参数:
|
||
1) 秘钥: 加密的时候用秘钥,解密的时候需要同样的秘钥才能解出来; 数据类型为bytes
|
||
2) 明文: 需要加密的参数; 数据类型为bytes
|
||
3) 模式: aes 加密常用的有 ECB 和 CBC 模式(我只用了这两个模式,还有其他模式);数据类型为aes类内部的枚举量
|
||
4) iv 偏移量: 这个参数在 ECB 模式下不需要,在 CBC 模式下需要;数据类型为bytes
|
||
|
||
1. 在Python中进行AES加密解密时,所传入的密文、明文、秘钥、iv偏移量、都需要是bytes(字节型)数据。python 在构建aes对象时也只能接受bytes类型数据。
|
||
2.当秘钥,iv偏移量,待加密的明文,字节长度不够16字节或者16字节倍数的时候需要进行补全。
|
||
3. CBC模式需要重新生成AES对象,为了防止这类错误,我写代码无论是什么模式都重新生成AES对象。
|
||
|
||
【编码模式】
|
||
由于python中的 AES 加密解密,只能接受字节型(bytes)数据。而我们常见的 待加密的明文可能是中文,或者待解密的密文经过base64编码的,这种都需要先进行编码或者解码,然后才能用AES进行加密或解密。
|
||
因此,在python使用AES进行加密或者解密时,都需要先转换成bytes型数据。
|
||
对于中文明文,我们可以使用encode()函数进行编码,将字符串转换成bytes类型数据。解密后,同样是需要decode()函数进行解码的,将字节型数据转换回中文字符(字符串类型)。
|
||
|
||
【填充模式】
|
||
前面我使用秘钥,还有明文,包括IV向量,都是固定16字节,也就是数据块对齐了。而填充模式就是为了解决数据块不对齐的问题,使用什么字符进行填充就对应着不同的填充模式
|
||
AES补全模式常见有以下几种:
|
||
1)ZeroPadding: 用b’\x00’进行填充,这里的0可不是字符串0,而是字节型数据的b’\x00’
|
||
2)PKCS7Padding: 当需要N个数据才能对齐时,填充字节型数据为N、并且填充N个
|
||
3)PKCS5Padding:与PKCS7Padding相同,在AES加密解密填充方面我没感到什么区别
|
||
4)no padding:当为16字节数据时候,可以不进行填充,而不够16字节数据时同ZeroPadding一样
|
||
|
||
|
||
"""
|
||
from Crypto.Cipher import AES
|
||
import base64
|
||
import binascii
|
||
|
||
|
||
# 数据类
|
||
class MData():
|
||
"""
|
||
MData类用于处理和转换数据。
|
||
它可以将数据保存到文件,从字符串或Base64编码的数据中加载数据,
|
||
并将数据转换为多种格式,如字符串、Base64编码的字符串或十六进制字符串。
|
||
"""
|
||
|
||
def __init__(self, data=b"", characterSet='utf-8'):
|
||
"""
|
||
初始化MData对象。
|
||
|
||
参数:
|
||
data (bytes): 初始数据,默认为空字节串。
|
||
characterSet (str): 字符编码,默认为'utf-8'。
|
||
"""
|
||
# data肯定为bytes
|
||
self.data = data
|
||
self.characterSet = characterSet
|
||
|
||
def saveData(self, FileName):
|
||
"""
|
||
将数据保存到指定的文件中。
|
||
|
||
参数:
|
||
FileName (str): 要保存数据的文件名。
|
||
"""
|
||
with open(FileName, 'wb') as f:
|
||
f.write(self.data)
|
||
|
||
def fromString(self, data):
|
||
"""
|
||
从字符串加载数据,并将其编码为字节串。
|
||
|
||
参数:
|
||
data (str): 要加载的字符串数据。
|
||
|
||
返回:
|
||
bytes: 编码后的字节串。
|
||
"""
|
||
self.data = data.encode(self.characterSet)
|
||
return self.data
|
||
|
||
def fromBase64(self, data):
|
||
"""
|
||
从Base64编码的字符串加载数据,并将其解码为字节串。
|
||
|
||
参数:
|
||
data (str): Base64编码的字符串数据。
|
||
|
||
返回:
|
||
bytes: 解码后的字节串。
|
||
"""
|
||
self.data = base64.b64decode(data.encode(self.characterSet))
|
||
return self.data
|
||
|
||
def fromHexStr(self, data):
|
||
"""
|
||
从十六进制字符串加载数据,并将其转换为字节串。
|
||
|
||
参数:
|
||
data (str): 十六进制字符串数据。
|
||
|
||
返回:
|
||
bytes: 转换后的字节串。
|
||
"""
|
||
self.data = binascii.a2b_hex(data)
|
||
return self.data
|
||
|
||
def toString(self):
|
||
"""
|
||
将字节串数据转换为字符串。
|
||
|
||
返回:
|
||
str: 转换后的字符串。
|
||
"""
|
||
return self.data.decode(self.characterSet)
|
||
|
||
def toBase64(self):
|
||
"""
|
||
将字节串数据转换为Base64编码的字符串。
|
||
|
||
返回:
|
||
str: 转换后的Base64编码的字符串。
|
||
"""
|
||
return base64.b64encode(self.data).decode()
|
||
|
||
def toHexStr(self):
|
||
"""
|
||
将字节串数据转换为十六进制字符串。
|
||
|
||
返回:
|
||
str: 转换后的十六进制字符串。
|
||
"""
|
||
return binascii.b2a_hex(self.data).decode()
|
||
|
||
def toBytes(self):
|
||
"""
|
||
获取字节串数据。
|
||
|
||
返回:
|
||
bytes: 数据的字节串形式。
|
||
"""
|
||
return self.data
|
||
|
||
def __str__(self):
|
||
"""
|
||
返回数据的字符串表示形式。
|
||
如果数据不能直接转换为字符串,则返回其Base64编码的字符串形式。
|
||
|
||
返回:
|
||
str: 数据的字符串或Base64编码的字符串形式。
|
||
"""
|
||
try:
|
||
return self.toString()
|
||
except Exception:
|
||
return self.toBase64()
|
||
|
||
|
||
### 封装类
|
||
class AEScryptor():
|
||
def __init__(self, key, mode, iv='', paddingMode="NoPadding", characterSet="utf-8"):
|
||
'''
|
||
构建一个AES对象
|
||
key: 秘钥,字节型数据
|
||
mode: 使用模式,只提供两种,AES.MODE_CBC, AES.MODE_ECB
|
||
iv: iv偏移量,字节型数据
|
||
paddingMode: 填充模式,默认为NoPadding, 可选NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding
|
||
characterSet: 字符集编码
|
||
'''
|
||
self.key = key
|
||
self.mode = mode
|
||
self.iv = iv
|
||
self.characterSet = characterSet
|
||
self.paddingMode = paddingMode
|
||
self.data = ""
|
||
|
||
def __ZeroPadding(self, data):
|
||
data += b'\x00'
|
||
while len(data) % 16 != 0:
|
||
data += b'\x00'
|
||
return data
|
||
|
||
def __StripZeroPadding(self, data):
|
||
data = data[:-1]
|
||
while len(data) % 16 != 0:
|
||
data = data.rstrip(b'\x00')
|
||
if data[-1] != b"\x00":
|
||
break
|
||
return data
|
||
|
||
def __PKCS5_7Padding(self, data):
|
||
needSize = 16 - len(data) % 16
|
||
if needSize == 0:
|
||
needSize = 16
|
||
return data + needSize.to_bytes(1, 'little') * needSize
|
||
|
||
def __StripPKCS5_7Padding(self, data):
|
||
paddingSize = data[-1]
|
||
return data.rstrip(paddingSize.to_bytes(1, 'little'))
|
||
|
||
def __paddingData(self, data):
|
||
if self.paddingMode == "NoPadding":
|
||
if len(data) % 16 == 0:
|
||
return data
|
||
else:
|
||
return self.__ZeroPadding(data)
|
||
elif self.paddingMode == "ZeroPadding":
|
||
return self.__ZeroPadding(data)
|
||
elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
|
||
return self.__PKCS5_7Padding(data)
|
||
else:
|
||
print("不支持Padding")
|
||
|
||
def __stripPaddingData(self, data):
|
||
if self.paddingMode == "NoPadding":
|
||
return self.__StripZeroPadding(data)
|
||
elif self.paddingMode == "ZeroPadding":
|
||
return self.__StripZeroPadding(data)
|
||
|
||
elif self.paddingMode == "PKCS5Padding" or self.paddingMode == "PKCS7Padding":
|
||
return self.__StripPKCS5_7Padding(data)
|
||
else:
|
||
print("不支持Padding")
|
||
|
||
def setCharacterSet(self, characterSet):
|
||
'''
|
||
设置字符集编码
|
||
characterSet: 字符集编码
|
||
'''
|
||
self.characterSet = characterSet
|
||
|
||
def setPaddingMode(self, mode):
|
||
'''
|
||
设置填充模式
|
||
mode: 可选NoPadding,ZeroPadding,PKCS5Padding,PKCS7Padding
|
||
'''
|
||
self.paddingMode = mode
|
||
|
||
def decryptFromBase64(self, entext):
|
||
'''
|
||
从base64编码字符串编码进行AES解密
|
||
entext: 数据类型str
|
||
'''
|
||
mData = MData(characterSet=self.characterSet)
|
||
self.data = mData.fromBase64(entext)
|
||
return self.__decrypt()
|
||
|
||
def decryptFromHexStr(self, entext):
|
||
'''
|
||
从hexstr编码字符串编码进行AES解密
|
||
entext: 数据类型str
|
||
'''
|
||
mData = MData(characterSet=self.characterSet)
|
||
self.data = mData.fromHexStr(entext)
|
||
return self.__decrypt()
|
||
|
||
def decryptFromString(self, entext):
|
||
'''
|
||
从字符串进行AES解密
|
||
entext: 数据类型str
|
||
'''
|
||
mData = MData(characterSet=self.characterSet)
|
||
self.data = mData.fromString(entext)
|
||
return self.__decrypt()
|
||
|
||
def decryptFromBytes(self, entext):
|
||
'''
|
||
从二进制进行AES解密
|
||
entext: 数据类型bytes
|
||
'''
|
||
self.data = entext
|
||
return self.__decrypt()
|
||
|
||
def encryptFromString(self, data):
|
||
'''
|
||
对字符串进行AES加密
|
||
data: 待加密字符串,数据类型为str
|
||
'''
|
||
self.data = data.encode(self.characterSet)
|
||
return self.__encrypt()
|
||
|
||
def __encrypt(self):
|
||
if self.mode == AES.MODE_CBC:
|
||
aes = AES.new(self.key, self.mode, self.iv)
|
||
elif self.mode == AES.MODE_ECB:
|
||
aes = AES.new(self.key, self.mode)
|
||
else:
|
||
print("不支持这种模式")
|
||
return
|
||
|
||
data = self.__paddingData(self.data)
|
||
enData = aes.encrypt(data)
|
||
return MData(enData)
|
||
|
||
def __decrypt(self):
|
||
if self.mode == AES.MODE_CBC:
|
||
aes = AES.new(self.key, self.mode, self.iv)
|
||
elif self.mode == AES.MODE_ECB:
|
||
aes = AES.new(self.key, self.mode)
|
||
else:
|
||
print("不支持这种模式")
|
||
return
|
||
data = aes.decrypt(self.data)
|
||
mData = MData(self.__stripPaddingData(data), characterSet=self.characterSet)
|
||
return mData
|
||
|