前言:账号密码一直对我们来说真的非常非常重要,但大多数人不是很重视,比如日常工作中,员工会经常登录到不同网站去查数据或者完成自己的工作,但是账号密码他们不一定会保存,经常会忘了。或者他们的密码都是名字拼音或者简单的数字,员工忘记密码管理员可以帮忙修改,但是密码在网上泄露那么会造成无可挽回的损失。自己平常也有很多站点的账号密码,以前是放在记事本,也试过放在gitee,感觉太危险了,万一丢了,或者忘了在哪就太麻烦了。为了方便自己,后来写了一个密码管理系统,用到了AES加密相关知识,对自己帮助挺大的,分享一下这块技术。
一、什么是AES
高级加密标准(AES,Advanced Encryption Standard),是一种最常见的对称加密算法,AES在世界各地的软件和硬件中实施加密敏感数据。
AES的加密流程介绍
1.明文P:没有经历加密的数据
2.密钥K:用来加密明文的密码,在对称加密算法中,加密与解密的密钥是相同的。
密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,通常是通过非对称加密算法加密密钥,
然后再通过网络传输给对方,或者直接面对面商量密钥。密钥是绝对不可以泄漏的,否则会被攻击者还原密文,窃取机密数据。
3.AES加密函数
经加密函数处理后的数据
4.AES解密函数
设AES解密函数为D,则 P = D(K, C),其中C为密文,K为密钥,P为明文。也就是说,把密文C和密钥K作为解密函数的参数输入,则解密函数会输出明文P。
AES基本的结构
AES为分组密码,分组密码就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说每个分组为16个字节(每个字节8位)。
AES 包括三种分组密码:AES-128、AES-192 和 AES-256。
AES-128使用128位密钥长度来加密和解密消息块
AES-192使用192位密钥长度来加密和解密消息块
AES-256使用256位密钥长度来加密和解密消息块
AES加密的模式
AES分为几种模式,比如ECB,CBC,CFB等等,这些模式除了ECB由于没有使用IV而不太安全,其他模式差别并没有太明显,大部分的区别在IV和KEY来计算密文的方法略有区别。
AES加密中IV的作用
IV称为初始向量,不同的IV加密后的字符串是不同的,加密和解密需要相同的IV,既然IV看起来和key一样,却还要多一个IV的目的,对于每个块来说,key是不变的,但是只有第一个块的IV是用户提供的,其他块IV都是自动生成。
IV的长度为16字节。超过或者不足,可能实现的库都会进行补齐或截断。但是由于块的长度是16字节,所以一般可以认为需要的IV是16字节。
AES加密中PADDING的作用
PADDING是用来填充最后一块使得变成一整块,所以对于加密解密两端需要使用同一的PADDING模式,大部分PADDING模式为PKCS5, PKCS7, NOPADDING。
AES加密和解密端
对于加密端,应该包括:加密秘钥长度,密钥,IV值,加密模式,PADDING方式。
对于解密端,应该包括:解密秘钥长度,密钥,IV值,解密模式,PADDING方式。
二、前端实现AES加密解密功能
前端要实现AES加密,需要下载crypto-js.js,crypto-js是一个纯javascript写的加密算法类库,可以非常方便地在javascript进行 MD5、SHA1、SHA2、SHA3、RIPEMD-160 哈希散列,进行 AES、DES、Rabbit、RC4、Triple DES 加解密
下载链接如下:
https://github.com/brix/crypto-js/releases
前端代码如下:
crypt_key = 'l36DoqKUYQP0N7e1'; crypt_iv = '131b0c8a7a6e072e'; //加密 function encrypt(data) { let aes_key = CryptoJS.enc.Utf8.parse(crypt_key); //解析后的key let new_iv = CryptoJS.enc.Utf8.parse(crypt_iv); //解析后的iv encrypted = CryptoJS.AES.encrypt(data, aes_key, { //AES加密 iv: new_iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding }); return encrypted.toString() } //解密 function decrypt(data) { let aes_key = CryptoJS.enc.Utf8.parse(crypt_key); // 解析后的key let aes_iv = CryptoJS.enc.Utf8.parse(crypt_iv); // 解析后的iv let baseResult=CryptoJS.enc.Base64.parse(data); // Base64解密 let ciphertext=CryptoJS.enc.Base64.stringify(baseResult); // Base64解密 let decryptResult = CryptoJS.AES.decrypt(ciphertext,aes_key, { // AES解密 iv: aes_iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding }); resData = decryptResult.toString(CryptoJS.enc.Utf8).toString(); return resData; }
测试下前端代码
console.log(encrypt('456')); 加密 console.log(decrypt('aCbhraRUHLvqxdoG5amBNQ==')); 解密
查看结果:
三、后端实现AES加密解密功能
import base64 import hashlib from Crypto.Cipher import AES, DES from binascii import b2a_hex, a2b_hex class DeAesCrypt: """ AES-128-CBC解密 """ def __init__(self, data, key, pad='zero'): """ :param data: 加密后的字符串 :param key: 随机的16位字符 :param pad: 填充方式 """ self.key = key self.data = data self.pad = pad.lower() hash_obj = hashlib.md5() # 构造md5对象 hash_obj.update(key.encode()) # 进行md5加密,md5只能对byte类型进行加密 res_md5 = hash_obj.hexdigest() # 获取加密后的字符串数据 self.iv = res_md5[:16] @property def decrypt_aes(self): """AES-128-CBC解密""" real_data = base64.b64decode(self.data) my_aes = AES.new(self.key, AES.MODE_CBC, self.iv) decrypt_data = my_aes.decrypt(real_data) return self.get_str(decrypt_data) def add_to_16(self,text): pad = 16 - len(text.encode('utf-8')) % 16 text = text + pad * chr(pad) return text.encode('utf-8') def encrypt(self): """AES-128-CBC加密""" # 预处理,填充明文为16的倍数 text = self.add_to_16(self.data) cryptor = AES.new(self.key,AES.MODE_CBC, self.iv) cipher_text = cryptor.encrypt(text) base = 16 if base == 16: # 返回16进制密文 return b2a_hex(cipher_text).decode('utf-8') elif base == 64: # 返回base64密文 return base64.b64encode(cipher_text).decode('utf-8') def get_str(self, bd): """解密后的数据去除加密前添加的数据""" if self.pad == "zero": # 去掉数据在转化前不足16位长度时添加的ASCII码为0编号的二进制字符 return ''.join([chr(i) for i in bd if i != 0]) elif self.pad == "pkcs7": # 去掉pkcs7模式中添加后面的字符 return ''.join([chr(i) for i in bd if i > 32]) else: return "不存在此种数据填充方式"
四、效果展示
前端添加用户名test,密码为123456
查看传送的数据是否加密,可以看到账号密码都已经加密过了
后端检查解密数据是否正确
username=DeAesCrypt(data=info_data.get('username'),key=info_data.get('aes_key')).decrypt_aes password=DeAesCrypt(data=info_data.get('password'),key=info_data.get('aes_key')).decrypt_aes {'username': 'test', 'password': '123456'}
详细AES加密知识可以查看下面链接
https://blog.csdn.net/qq_28205153/article/details/55798628
补充:
由于 PyCrypto 已经超过三年无人维护,因此 Github 上的开发者 Varbin 在该项目的 Github issue 里呼吁开发们不要再使用 PyCrypto ,而应该将 PyCrypto 替换为 PyCryptodome。
对于使用 PyCrypto 的已有项目而言,PyCryptodome 保持了与 PyCrypto 相当高的兼容性并且处于良好的维护状态,因此便于更换。对于要使用 Python 加密库的新项目,建议开发者使用 PyCryptodome 或者 cryptography。