前言:賬號密碼一直對我們來說真的非常非常重要,但大多數人不是很重視,比如日常工作中,員工會經常登錄到不同網站去查數據或者完成自己的工作,但是賬號密碼他們不一定會保存,經常會忘了。或者他們的密碼都是名字拼音或者簡單的數字,員工忘記密碼管理員可以幫忙修改,但是密碼在網上泄露那么會造成無可挽回的損失。自己平常也有很多站點的賬號密碼,以前是放在記事本,也試過放在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。
