再过30分钟你就能了解python中的AES、RSA加密解密啦

389 阅读11分钟

1. AES

对称加密,就是说,A用密码对数据进行AES加密后,B用同样的密码对密文进行AES解密

pycrypto库就是Crypto的python版本,但该版本已经停止更新,可以弃用了。

pycrytodome库才是真正的Crypto的最新版本,pycryptodom是包含了原来pycrypto库和后来的更新功能的。所以不要同时安装这个两个库,避免冲突。

pycryptodomex库也是Crypto的最新版本,但pycryptodomex不包含原来的pycrypto库,所以要和原来的pycrypto同时安装,这个库是用作已经安装了pycrypto库,升级使用的。

1.1 依赖

pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome

1.2. 加密模式

AES比DES安全性高,运算速度快,一般就用AES。

AES 加密最常用的模式就是 ECB模式 和 CBC 模式,ECB模式和CBC 模式俩者区别就是 ECB 不需要 iv偏移量,而CBC需要。

1.ECB模式(电子密码本模式:Electronic codebook)

ECB是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。

2.CBC模式(密码分组链接:Cipher-block chaining)

CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或。

3.CFB模式(密文反馈:Cipher feedback)

与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文(Stream Cipher)。

4.OFB模式(输出反馈:Output feedback)

OFB是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。

实际上AES加密有AES-128、AES-192、AES-256三种,分别对应三种密钥长度128bits(16字节)、192bits(24字节)、256bits(32字节)。当然,密钥越长,安全性越高,加解密花费时间也越长。默认的是AES-128,其安全性完全够用。

1.3. 填充模式

一般使用秘钥,还有明文,包括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字节) 除了no padding 填充模式,剩下的填充模式都会填充到下一次数据块对齐为止,而不会出现不填充的问题。 PKCS7Padding和 PKCS5Padding需要填充字节对应表:

明文长度值(mod 16)添加的填充字节数每个填充字节的值
0160x10
1150x0F
2140x0E
3130x0D
4120x0C
5110x0B
6100x0A
790x09
880x08
970x07
1060x06
1150x05
1240x04
1330x03
1420x02
1510x01

当明文长度值已经对齐时(mod 16 = 0),还是需要进行填充,并且填充16个字节值为0x10。ZeroPadding填充逻辑也是类似的,只不过填充的字节值都为0x00,在python表示成 b'\x00'。 填充完毕后,就可以使用 AES进行加密解密了,当然解密后,也需要剔除填充的数据

1.4. AES加密解密和填充完整示例

方便使用可以直接pip安装SomeTools,已包含所需代码功能

pip install SomeTools


from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from sometools.sync_tools.base import Base

class EncryptionDecryptionMixIn(Base):

    def __init__(self, *args, **kwargs):
        super(EncryptionDecryptionMixIn, self).__init__(*args, **kwargs)

    @staticmethod
    def aes_ecb_encryption(content, password: bytes) -> bytes:
        """
        ECB模式加密
        :param content:明文必须为16字节或者16字节的倍数的字节型数据,如果不够16字节需要进行补全
        :param password:秘钥必须为16字节或者16字节的倍数的字节型数据。
        :return: bytes
        """
        print("ECB模式加密")
        print("明文:", content)  # 加密明文,bytes类型
        aes = AES.new(password, AES.MODE_ECB)  # 创建一个aes对象,aes 加密常用的有 ECB 和 CBC 模式,AES.MODE_ECB 表示模式是ECB模式
        en_text = aes.encrypt(content)  # 加密明文
        print("密文:", en_text)  # 加密明文,bytes类型
        return en_text

    @staticmethod
    def aes_ecb_decryption(en_text, password: bytes) -> bytes:
        """
        ECB模式解密
        :param en_text 加密后的密文
        :param password:秘钥必须为16字节或者16字节的倍数的字节型数据。
        :return: bytes
        """
        print("ECB模式解密")
        print("密文:", en_text)
        aes = AES.new(password, AES.MODE_ECB)
        content = aes.decrypt(en_text)
        print("明文:", content)
        return content

    @staticmethod
    def aes_cbc_encryption(content, password, iv: bytes) -> bytes:
        """
        CBC模式的加密
        :param content:明文必须为16字节或者16字节的倍数的字节型数据,如果不够16字节需要进行补全
        :param password:秘钥必须为16字节或者16字节的倍数的字节型数据
        :param iv 偏移量,bytes类型
        :return: bytes
        """
        print("CBC模式的加密")
        print("明文:", content)  # 加密明文,bytes类型
        aes = AES.new(password,AES.MODE_CBC,iv) #创建一个aes对象  AES.MODE_CBC 表示模式是CBC模式
        en_text = aes.encrypt(content)  # 加密明文
        print("密文:", en_text)  # 加密明文,bytes类型
        return en_text


    @staticmethod
    def aes_cbc_decryption(en_text, password, iv: bytes) -> bytes:
        """
        CBC模式解密
        :param en_text 加密后的密文
        :param password:秘钥必须为16字节或者16字节的倍数的字节型数据。
        :param iv 偏移量,bytes类型
        :return: bytes
        """
        print("CBC模式解密")
        print("密文:", en_text)
        aes = AES.new(password,AES.MODE_CBC,iv)  # CBC模式与ECB模式的区别:AES.new() 解密和加密重新生成了aes对象,加密和解密不能调用同一个aes对象,否则会报错TypeError: decrypt() cannot be called after encrypt()
        content = aes.decrypt(en_text)
        print("明文:", content)
        return content

    @staticmethod
    def aes_pad(content) -> bytes:
        """
        填充
        """
        print(f"填充前:{content}")
        text = pad(content, AES.block_size)
        print(f"填充后:{text}")
        return text

    @staticmethod
    def aes_unpad(en_text) -> bytes:
        """
        去填充
        """
        print(f"去填充前:{en_text}")
        text = unpad(en_text, AES.block_size)
        print(f"去填充后:{text}")
        return text

if __name__ == '__main__':
    demo_ins = EncryptionDecryptionMixIn()
    content = b'abcdefghijklmnh'
    res_text = demo_ins.aes_pad(content)
    demo_ins.aes_unpad(res_text)
    # 加密解密
    password = b'1234567812345678'  # 秘钥,b就是表示为bytes类型,秘钥必须为16字节或者16字节的倍数的字节型数据。
    content = b'abcdefghijklmnhi'  # 需要加密的内容,bytes类型,明文必须为16字节或者16字节的倍数的字节型数据,如果不够16字节需要进行补全
    en_text = demo_ins.aes_ecb_encryption(content, password)
    demo_ins.aes_ecb_decryption(en_text, password)
    iv = b'1234567812345678'  # iv偏移量,bytes类型
    en_text = demo_ins.aes_cbc_encryption(content, password, iv)
    content = demo_ins.aes_cbc_decryption(en_text, password, iv)

    # 中文加密解密
    # 我们可以使用encode()函数进行编码,将字符串转换成bytes类型数据
    # 这里选择gbk编码,是为了正好能满足16字节
    # utf8编码是一个中文字符对应3个字节,utf8 和 gbk 编码,针对英文字符编码都是一个字符对应一个字节。这里为了举例所以才选择使用gbk编码
    # 在解密后,同样是需要decode()函数进行解码的,将字节型数据转换回中文字符(字符串类型)
    print("中文明文:好好学习天天向上 需要gbk编码")
    content = "好好学习天天向上".encode('gbk')  # gbk编码,是1个中文字符对应2个字节,8个中文正好16字节
    en_text = demo_ins.aes_ecb_encryption(content, password)
    content = demo_ins.aes_ecb_decryption(en_text, password)
    print("中文明文:", content.decode("gbk"),"解密后同样需要进行解码")  # 解密后同样需要进行解码

2. RSA

RSA 加密机制:属于非对称加密,公钥用于对数据进行加密,私钥对数据进行解密,两者不可逆。公钥和私钥是同时生成的,且一一对应。比如:A拥有公钥,B拥有公钥和私钥。A将数据通过公钥进行加密后,发送密文给B,B可以通过私钥和公钥进行解密。

非对称加密比对称加密更安全、但速度慢千倍、通常用来做身份认证

pip install rsa

一般使用比较简单,看代码即可

2.1 公钥加密、私钥解密

方便使用可以直接pip安装SomeTools,已包含所需代码功能

pip install SomeTools

from rsa.key import PublicKey, PrivateKey
from sometools.sync_tools.base import Base


class RsaMixIn(Base):

    def __init__(self, *args, **kwargs):
        super(RsaMixIn, self).__init__(*args, **kwargs)

    def rsa_get_key_pair(self) -> (PublicKey, PrivateKey):
        """生成密钥对"""
        pubkey, prikey = rsa.newkeys(1024)
        return pubkey, prikey

    def rsa_encryption(self, content: bytes, pubkey: PublicKey) -> bytes:
        """加密:使用公钥"""
        return rsa.encrypt(content, pubkey)

    def rsa_decryption(self, en_text: bytes, prikey: PrivateKey) -> bytes:
        """解密:使用私钥"""
        return rsa.decrypt(en_text, prikey)
        
demo_ins = RsaMixIn()
#RSA加密解密
print(f"\n")
print(f"RSA加密解密 使用bytes")
# 生成密钥对
print(f"生成密钥对")
pubkey, prikey = demo_ins.rsa_get_key_pair()
print(f"公钥{pubkey}")
print(f"私钥{prikey}")
content = 'Welcome to RSA'
print(f"明文:{content}")
print(f"加密:使用公钥")
en_text = demo_ins.rsa_encryption(content.encode(), pubkey)
print(f"加密后{en_text}")
print(f"解密:使用私钥")
print(f"密文{en_text}")
content = demo_ins.rsa_decryption(en_text, prikey)
print(f"解密后{content.decode()}")

2.2 加签、验签

import rsa
from rsa.key import PublicKey, PrivateKey
from sometools.sync_tools.base import Base


class RsaMixIn(Base):

    def __init__(self, *args, **kwargs):
        super(RsaMixIn, self).__init__(*args, **kwargs)

    def rsa_get_key_pair(self) -> (PublicKey, PrivateKey):
        """生成密钥对"""
        pubkey, prikey = rsa.newkeys(1024)
        return pubkey, prikey

    def rsa_sign(self, content: bytes, pri_key: PrivateKey, sign_type: str = 'MD5') -> bytes:
        """加签    rsa.sign(原信息,私钥,加密方式)  生成加签过后的信息"""
        sign_message = rsa.sign(content, pri_key, sign_type)
        return sign_message

    def rsa_verify(self, content: bytes, pub_key: PublicKey, sign_message: bytes) -> (PublicKey, PrivateKey):
        """验签    rsa.verify(需要验证的信息,加签过后的信息,公钥),如果需要验证的信息,是原信息,返回加密方式"""
        try:
            veri_res = rsa.verify(content, sign_message, pub_key)
            print(f'校验通过无修改-加密方式{veri_res}')
            return True
        except rsa.pkcs1.VerificationError as e:
            print(f"校验未通过-信息被篡改过-{e}")
            return False



#RSA加签、验签
print(f"\n")
print(f"RSA加签 使用bytes")
pubkey, prikey = demo_ins.rsa_get_key_pair()
content = '本周美联储会降息'
print(f"内容:{content}")
sign_message = demo_ins.rsa_sign(content.encode(), prikey)
print(f"RSA加签后签名信息:{sign_message}")
print(f"RSA验证 使用未被修改过的内容")
print(f"内容:{content}")
sign_res = demo_ins.rsa_verify(content.encode(), pubkey,sign_message)
if sign_res:
	print(f"校验通过")
else:
	print(f"校验未能通过")
print(f"RSA验证 使用被修改过的内容")
content = '本周美联储不会降息'
print(f"内容:{content}")
sign_res = demo_ins.rsa_verify(content.encode(), pubkey,sign_message)
if sign_res:
	print(f"校验通过")
else:
	print(f"校验未能通过")

3. 混合加密

单纯的使用 RSA(非对称加密)方式的话,效率会很低,因为非对称加密解密方式虽然很保险,但是过程复杂,需要时间长,但是,RSA 优势在于数据传输安全,且对于几个字节的数据,加密和解密时间基本可以忽略,所以用它加密 AES 秘钥(一般16个字节)再合适不过了,单纯的使用 AES(对称加密)方式的话,死板且不安全。这种方式使用的密钥是一个固定的密钥,客户端和服务端是一样的,一旦密钥被人获取,那么,我们所发的每一条数据都会被都对方破解,但是,AES有个很大的优点,那就是加密解密效率很高,我们传输正文数据时需要这种加解密效率高的,所以这种方式适合用于传输量大的数据内容,基于以上特点,择优取之,就成就了混合加密的思路。

利用 RSA 来加密传输 AES的密钥,用 AES的密钥 来加密数据。 这样做:既利用了 RSA 的灵活性,可以随时改动 AES 的密钥;又利用了 AES 的高效性,可以高效传输数据。