实际项目中为了系统安全,我们经常需要对请求数据和响应数据做加密处理,这里以spring后台,vue前台的java web为例,记录一个实现过程
一、为什么要结合AES和RSA?
因为AES是对称加密,即加密解密用的秘钥是一样,这样一来AES的秘钥保管尤其重要,但是AES有个很好的优点,就是处理效率高。而RSA是不对称加密,即加密解密用的秘钥不一样,分别叫公钥和私钥,通常用公钥加密,然后用私钥解密,其中公钥可以公开,只需要保管好私钥即可,而相比AES而言RSA速度慢效率低。所以,通常我们结合这两种加密方式的优点来完成数据的安全传输。
二、AES和RSA的结合使用过程
1.前端随机动态生成aesKey:因为AES的加密解密秘钥需要一致,如果整个系统写死AES的秘钥会很不安全,所以每次请求动态生成aesKey会比较好
2.前端用RSA对动态aesKey加密:动态aesKey需要传到后端供解密,传输过程用RSA加密
3.前端保存动态aesKey:因为同一个请求的响应需要一样的aesKey解密,所以前端还得把动态aesKey保存下来,可以再随机生成一个id,然后按键值对的方式保存在前端变量中{id: aesKey}
4.把加密的aesKey和id放到请求头
5.后端用RSA私钥解密得明文aesKey:后端从请求头取出加密的aesKey,然后用私钥解密拿到明文的aesKey,然后对请求数据解密
6.后端用明文aesKey加密响应数据
7.后端把请求过来的id放到响应头
8.前端根据响应头的id,取到对应的aesKey,对响应数据解密
9.前端删除动态的aesKey
三、具体实现案例
1.前端实现AES和RSA的公共方法
aesUtils.js:
const CryptoJS = require('crypto-js')
function GetRandomNum (n) {
let chars = [
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F',
'G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V',
'W','X','Y','Z','.','?','~','!','@','#','$','%','^','&','*']
if(n == null) {
n = 16
}
let res = ""
for(let i = 0; i < n ; i++) {
let id = Math.ceil(Math.random()*46)
res += chars[id]
}
return res
}
function GetUuid () {
let s = []
const hexDigits = '0123456789abcdef'
for (let i = 0; i < 36; i++) {
let indexStart = Math.floor(Math.random() * 0x10)
s[i] = hexDigits.substring(indexStart, indexStart+1)
}
s[14] = '4'
let indexStart = (s[19] & 0x3) | 0x8
s[19] = hexDigits.substring(indexStart, indexStart+1)
s[8] = s[13] = s[18] = s[23] = '-'
return s.join('')
}
function Decrypt (word, key, iv) {
let key = CryptoJS.enc.Utf8.parse(key)
let base64 = CryptoJS.enc.Base64.parse(word)
let src = CryptoJS.enc.Base64.stringify(base64)
var decrypt = CryptoJS.AES.decrypt(src, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding })
var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
return decryptedStr.toString()
}
function Encrypt (word, key, iv) {
let key = CryptoJS.enc.Utf8.parse(key)
let src = CryptoJS.enc.Utf8.parse(word)
var encrypted = CryptoJS.AES.encrypt(src, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding })
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
}
export default {
Decrypt,
Encrypt,
GetRandomNum,
GetUuid,
}
rsaUtils.js:
import JSEncrypt from 'jsencrypt'
const pubKey = `MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMYWwlqtkWIdA0I/54TP/k1VLgyNwzQB1IvrVKdNfobivHzN02VFGAED1hDSLDiSp4yYrFcXmMFReJJOJ1zjvWECAwEAAQ==`
const encrypt = new JSEncrypt()
encrypt.setPublicKey(pubKey)
function Encrypt(str) {
let data = encrypt.encrypt(str.toString())
return data
}
function Decrypt(str) {
let data = encrypt.decrypt(str.toString())
return data
}
export default {
Encrypt,
Decrypt,
}
2.前端在拦截请求处,生成AES随机秘钥并加密请求数据,再用RSA加密AES秘钥并放到请求头
fetch.js:
const aesKeys = {}
instance.interceptors.request.use(function(request) {
let randomKey = aes.GetRandomNum()
let reqData
if (request.data instanceof Object) {
reqData = JSON.stringify(request.data)
} else {
reqData = request.data
}
request.data = aes.Encrypt(reqData, randomKey, randomKey)
let uuid = aes.GetUuid()
let encryptAesKey = rsa.Encrypt(randomKey)
request.headers['Encrypt