【python模块----AES加密】

AES:

高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥,具体的加密流程如下图:

img

  1. 明文
    有意义的字符或比特集,或通过某种公开的编码标准就能获得的消息。
  2. 密钥
    密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。
  3. 密文
    对明文通过密钥加密而产生的不可直接理解的字符或比特集。在对称加密算法中通过相同密钥进行解密便可获得对应明文。
  4. AES加密函数
    设AES加密函数为E,则 C = E(K, P)。也就是说,把明文P和密钥K作为加密函数的参数输入,则加密函数E会输出密文C。
  5. AES解密函数
    设AES解密函数为D,则 P = D(K, C)。也就是说,把密文C和密钥K作为解密函数的参数输入,则解密函数会输出明文P。

对称加密:

特性:加密和解密采用相同密钥
优点:算法公开、计算量小、加密速度快、加密效率高
缺点:密钥的管理
(密文的传输无论是速率还是安全性不是问题,但攻击者一旦获得密钥便可由对称加密算法的优点:算法公开、计算量小解密获得明文,导致每次传输必须使用其他人无法获取的唯一密钥,这会使得发收信双方所拥有的密钥数量呈几何级数增长,密钥的数量和密钥的传输是密钥的管理的两大痛点)

非对称加密:加密和解密使用的是两个不同的密钥的算法叫作非对称加密算法。
非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。公钥和密钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用公钥对数据进行加密,只有用对应的私钥才能解密。
对称加密算法示意图如下:
非对称加密算法

from Crypto.Cipher import AES
import base64
# ECB模式加密
password = b'1234567812345678' #秘钥,b就是表示为bytes类型
text = b'abcdefghijklmnhi' #需要加密的内容,bytes类型
aes = AES.new(password,AES.MODE_ECB) #创建一个aes对象
# AES.MODE_ECB 表示模式是ECB模式
en_text = aes.encrypt(text) #加密明文
print("密文:",en_text) #加密明文,bytes类型
den_text = aes.decrypt(en_text) # 解密密文
print("明文:",den_text)
"""
秘钥必须为16字节或者16字节的倍数的字节型数据。
明文必须为16字节或者16字节的倍数的字节型数据,如果不够16字节需要进行补全,关于补全规则,后面会在补全模式中具体介绍。
"""
"""
CBC模式的例子,可以简单看出CBC模式与ECB模式的区别:AES.new() 解密和加密重新生成了aes对象,加密和解密不能调用同一个aes对象,
否则会报错TypeError: decrypt() cannot be called after encrypt()。
"""

# CBC模式
key = b'1234567812345678'  # 秘钥 bytes类型
# 偏移量
vi = b'1234567812345678'
# 加密的内容
text = b'abcdefghijklimnl'
# 创建一个aes对象
aes = AES.new(key,AES.MODE_CBC,vi)
# 加密
en_text = aes.encrypt(text)
print('密文:',en_text)
# CBC模式下解密需要重新创建一个aes对象
aes = AES.new(key,AES.MODE_CBC,vi)
den_text = aes.decrypt(en_text)
print("明文:",den_text)
"""
1. 在Python中进行AES加密解密时,所传入的密文、明文、秘钥、iv偏移量、都需要是bytes(字节型)数据。python 在构建aes对象时也只能接受bytes类型数据。

2.当秘钥,iv偏移量,待加密的明文,字节长度不够16字节或者16字节倍数的时候需要进行补全。

3. CBC模式需要重新生成AES对象,为了防止这类错误,我写代码无论是什么模式都重新生成AES对象。
"""
"""
python中的 AES 加密解密,只能接受字节型(bytes)数据。而我们常见的 
待加密的明文可能是中文,或者待解密的密文经过base64编码的,
这种都需要先进行编码或者解码,然后才能用AES进行加密或解密。反正无论是什么情况,
在python使用AES进行加密或者解密时,都需要先转换成bytes型数据。
"""

# ECB模式 对中文进行加密解密
"""
对于中文明文,我们可以使用encode()函数进行编码,将字符串转换成bytes类型数据,
而这里我选择gbk编码,是为了正好能满足16字节,utf8编码是一个中文字符对应3个字节。
这里为了举例所以才选择使用gbk编码。

在解密后,同样是需要decode()函数进行解码的,将字节型数据转换回中文字符(字符串类型)。
"""

key = b'1234567812345678'
# 加密数据
text = '好好学习天天向上'.encode('gbk')  # gbk编码 1个中文对应2个字节
aes = AES.new(key,AES.MODE_ECB)
en_text = aes.encrypt(text)  # 加密
print("密文:",en_text)
# 解密
den_text = aes.decrypt(en_text)
print("明文:",den_text.decode('gbk'))  # 解密后同样需要进行解码

"""
现在我们来看另外一种情况,密文是经过base64编码的(这种也是非常常见的,很多网站也是这样使用的),
我们用 http://tool.chacuo.net/cryptaes/ 这个网站举例子:
模式:ECB

密码: 1234567812345678

字符集:gbk编码

输出: base64

我们来写一个python 进行aes解密:
"""
from Crypto.Cipher import AES
import  base64
key = b'1234567812345678'
aes = AES.new(key,AES.MODE_ECB)
# 解密
en_text = b"XWgWch2M1jqU/OEJCY7yew=="  # 乞力马扎罗的雪
en_text = base64.decodebytes(en_text)  # 进行base64 解码,返回值依然是bytes类型
den_text = aes.decrypt(en_text)
print("明文:",den_text.decode('gbk'))
# base64 编码解码 和 hexstr 编码解码
import base64
import binascii
data = "hello".encode()
data = base64.b64encode(data)
print("base64编码:",data)
data = base64.b64decode(data)
print("base64解码:",data)
data = binascii.b2a_hex(data)
print("hexstr编码:",data)
data = binascii.a2b_hex(data)
print("hexstr解码:",data)

"""
填充模式
前面我使用秘钥,还有明文,包括IV向量,都是固定16字节,也就是数据块对齐了。而填充模式就是为了解决数据块不对齐的问题,使用什么字符进行填充就对应着不同的填充模式

AES补全模式常见有以下几种:

模式	意义
ZeroPadding	用b’\x00’进行填充,这里的0可不是字符串0,而是字节型数据的b’\x00’
PKCS7Padding	当需要N个数据才能对齐时,填充字节型数据为N、并且填充N个
PKCS5Padding	与PKCS7Padding相同,在AES加密解密填充方面我没感到什么区别
no padding	当为16字节数据时候,可以不进行填充,而不够16字节数据时同ZeroPadding一样
这里有一个细节问题,我发现很多文章说的也是不对的。

ZeroPadding填充模式的意义:很多文章解释是当为16字节倍数时就不填充,
然后当不够16字节倍数时再用字节数据0填充,这个解释是不对的,
这解释应该是no padding的,而ZeroPadding是不管数据是否对其,都进行填充,
直到填充到下一次对齐为止,也就是说即使你够了16字节数据,它会继续填充16字节的0,
然后一共数据就是32字节。

这里可能会有一个疑问,为什么是16字节 ,其实这个是 数据块的大小,
网站上也有对应设置,网站上对应的叫128位,也就是16字节对齐,
当然也有192位(24字节),256位(32字节)。

"""
"""
python 完整实现
"""
from Crypto.Cipher import AES
import base64
import binascii


# 数据类
class MData():
    def __init__(self, data=b"", characterSet='utf-8'):
        # data肯定为bytes
        self.data = data
        self.characterSet = characterSet

    def saveData(self, FileName):
        with open(FileName, 'wb') as f:
            f.write(self.data)

    def fromString(self, data):
        self.data = data.encode(self.characterSet)
        return self.data

    def fromBase64(self, data):
        self.data = base64.b64decode(data.encode(self.characterSet))
        return self.data

    def fromHexStr(self, data):
        self.data = binascii.a2b_hex(data)
        return self.data

    def toString(self):
        return self.data.decode(self.characterSet)

    def toBase64(self):
        return base64.b64encode(self.data).decode()

    def toHexStr(self):
        return binascii.b2a_hex(self.data).decode()

    def toBytes(self):
        return self.data

    def __str__(self):
        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


if __name__ == '__main__':
    key = b"1234567812345678"
    iv = b"0000000000000000"
    aes = AEScryptor(key, AES.MODE_CBC, iv, paddingMode="ZeroPadding", characterSet='utf-8')

    data = "乞力马扎罗的雪"
    rData = aes.encryptFromString(data)
    print("密文:", rData.toBase64())
    rData = aes.decryptFromBase64(rData.toBase64())
    print("明文:", rData)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值