记录一下AES加解密在python中的使用
研究AES之前先了解下常用的md5加密,既。然谈到md5,就必须要知道python3中digest()和hexdigest()区别。
hash.digest()
返回摘要,作为二进制数据字符串值hash.hexdigest()
返回摘要,作为十六进制数据字符串值
# hashlib是涉及安全散列和消息摘要,提供多个不同的加密算法接口,如SHA1、SHA224、SHA256、SHA384、SHA512、MD5等。
import hashlib
md5 = hashlib.md5()
md5.update("test".encode('utf-8'))
print(u"digest返回的摘要:%s"% md5.digest())
print(u"hexdigest返回的摘要:%s"% md5.hexdigest())
AES:密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
AES分为几种模式,比如ECB,CBC,CFB、PGP、OFB、CTR等等这个我们可以点击源码即可看到。
#: Electronic Code Book (ECB). See `blockalgo.MODE_ECB`.
MODE_ECB = 1
#: Cipher-Block Chaining (CBC). See `blockalgo.MODE_CBC`.
MODE_CBC = 2
#: Cipher FeedBack (CFB). See `blockalgo.MODE_CFB`.
MODE_CFB = 3
#: This mode should not be used.
MODE_PGP = 4
#: Output FeedBack (OFB). See `blockalgo.MODE_OFB`.
MODE_OFB = 5
#: CounTer Mode (CTR). See `blockalgo.MODE_CTR`.
MODE_CTR = 6
#: OpenPGP Mode. See `blockalgo.MODE_OPENPGP`.
MODE_OPENPGP = 7
对于流加密,需要将分组密码转化为流模式工作。对于块加密(或称分组加密),如果要加密超过块大小的数据,就需要涉及填充和链加密模式。
- ECB(Electronic Code Book电子密码本)模式
ECB模式是最早采用和最简单的模式,它将加密的数据分成若干组,每组的大小跟加密密钥长度相同,然后每组都用相同的密钥进行加密。
优点:
1.简单;
2.有利于并行计算;
3.误差不会被传送;
缺点:
1.不能隐藏明文的模式;
2.可能对明文进行主动攻击; 因此,此模式适于加密小消息。
- CBC(Cipher Block Chaining,加密块链)模式
优点:
1.不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。
缺点:
1.不利于并行计算;
2.误差传递;
3.需要初始化向量IV
- CFB(Cipher FeedBack Mode,加密反馈)模式
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.误差传送:一个明文单元损坏影响多个单元;
3.唯一的IV;
OFB(Output FeedBack,输出反馈)模式
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.对明文的主动攻击是可能的;
3.误差传送:一个明文单元损坏影响多个单元 [4] 。
先说一下我踩得坑,我的版本是python3.7.9,之所以在引入的时候加了个备注# pycryptodome
,是因为使用过程中我发现有的python环境需要装pycryptodome
这个包,但引用AES的时候又用的是Crypto.Cipher
,这里记录下这个坑。
from Crypto.Cipher import AES # pycryptodome
PADDING
AES块加密说过,PADDING是用来填充最后一块使得变成一整块,所以对于加密解密两端需要使用同一的PADDING模式,大部分PADDING模式为PKCS5, PKCS7, NOPADDING。
pkcs5padding和pkcs7padding的区别
pkcs5padding
和pkcs7padding
都是用来填充数据的一种模式。在ECB中,数据是分块加密的。如果需要加密的数据的字节码的长度不是块大小的整数倍就需要填充。
使用PKCS5,填充时:
要填充7个字节,那么填入的值就是0×7;
如果只填充1个字节,那么填入的值就是0×1;
恰好8个字节时还要补8个字节的0×08
正是这种即使恰好是8个字节也需要再补充字节的规定,可以让解密的数据很确定无误的移除多余的字节。
PKCS7和PKCS5的区别是数据块的大小;
- PKCS5填充块的大小为8bytes(64位)
- PKCS7填充块的大小可以在1-255bytes之间。
因为AES并没有64位的块, 如果采用PKCS5, 那么实质上就是采用PKCS7
python实现
安装所需要的包
pip install pycryptodome
python代码
# -*- coding:utf-8 -*-
import base64
from Crypto.Cipher import AES
class EncryptDate:
def __init__(self, key):
self.key = key # 初始化密钥
self.length = AES.block_size # 初始化数据块大小
self.aes = AES.new(self.key, AES.MODE_ECB) # 初始化AES,ECB模式的实例
# 截断函数,去除填充的字符
self.unpad = lambda date: date[0:-ord(date[-1])]
def pad(self, text):
"""
#填充函数,使被加密数据的字节码长度是block_size的整数倍
"""
count = len(text.encode('utf-8'))
add = self.length - (count % self.length)
entext = text + (chr(add) * add)
return entext
def encrypt(self, encrData): # 加密函数
res = self.aes.encrypt(self.pad(encrData).encode("utf8"))
msg = str(base64.b64encode(res), encoding="utf8")
return msg
def decrypt(self, decrData): # 解密函数
res = base64.decodebytes(decrData.encode("utf8"))
msg = self.aes.decrypt(res).decode("utf8")
return self.unpad(msg)
def sm3(text):
if text is None or len(text) < 5:
return ''
return Encode.SM3_add_len(text).upper()
def md5(text):
if text is None or len(text) < 5:
return ''
a = hashlib.md5()
# 计算md5值
a.update(text.encode(encoding='utf-8'))
# 返回摘要,作为十六进制数据字符串值
return a.hexdigest().upper()
密文示例:
密钥为 1234567890ABCEDF1234567890ABCEDF
- 明文为 123456,加密后密文为:
swqp5cQ56HME/pL99VuHkA==
- 密文为:
swqp5cQ56HME/pL99VuHkA==
,解密后明文为 123456
可以看到代码中用到了SM3_add_len()
这里提供下SM3_add_len()
的源码,其实就是用字符本身的长度拼接字符本身
def SM3_add_len(src):
msg=str(src)
msg=str(len(msg))+msg
return Encode.SM3(msg)
测试
test_key = "1234567890ABCEDF1234567890ABCEDF"
test_txt = "123456"
eg2 = EncryptDate(test_key) # 这里密钥的长度必须是16的倍数
en2 = eg2.encrypt(test_txt) # 对123456进行加密,判断是否加密后等于swqp5cQ56HME/pL99VuHkA==
print("加密:", en2)
print("解密:", eg2.decrypt(en2))
assert en2 == 'swqp5cQ56HME/pL99VuHkA==' # 判断是否加密后等于swqp5cQ56HME/pL99VuHkA==
assert eg2.decrypt(en2) == test_txt # 对加密后的密文解密,判断是否等于123456
test_bank_card = '6222222222222222222'
print(sm3(test_bank_card))
# 判断6222222222222222222 经过ECB/PKCS5Padding加密后是否等于5A7E1B360D38CBFDFF078A197283C772C40A4903C1C2D9A082977F209508AFC1
assert sm3(test_bank_card) == '5A7E1B360D38CBFDFF078A197283C772C40A4903C1C2D9A082977F209508AFC1'
# 判断110000201912010000 经过md5加密后是否等于E8C8E29576C886A87A80C39B405F0A19
id_no = '110000201912010000'
print(md5(id_no))
assert md5(id_no) == 'E8C8E29576C886A87A80C39B405F0A19'
ph_no = '13800000000'
print(md5(ph_no))
assert md5(ph_no) == '5DAAD257487F1B493114181A22E37EB5'
可以看到我这里测试用了断言(assert),这个我上一篇文章刚讲过,主要是判断如果它的条件返回错误,则终止程序执行。