为什么需要对前后端接口加解密?主要是甲方要求。
AES是最常见的对称加密算法,加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合,且加密长文本比较方便。缺点是密钥的传输比较麻烦,只能将一把公钥分别在前后端代码中各存放一份,还有一个遇到的问题下面会讲到,那就是美国对AES加密密钥长度的限制。优点是加密效率高,缺点是安全性低。
RSA也就是非对称加密算法,是使用不同密钥进行加密和解密的算法,也称为公私钥加密。公钥和私钥是同时生成的,公钥用来加密,私钥用来解密,加解密的密钥是成对出现。但是不推荐用RSA加密请求报文,因为RSA加密后的报文会很长,经常会出现超过请求体长度限制。优点是安全性高,缺点是RSA的加密效率低。
我们可以结合两者的优点,AES的加密效率高,那我们可以使用aes来加密报文,而RSA的灵活性和安全性来加密aes的密钥。总的思路就是利用RSA来加密传输AES的密钥,用 AES的密钥来加密请求报文。
首先这个功能我们的出发点是可以灵活配置,所以我们在需要加密的请求头添加一个加密标识代码如下:
import axios from '@common/plugins/Axios'
saveStudent: data => {
return axios.request({
url: `/demo/saveStudent`,
method: 'post',
headers: {
//需要加密的请求在头部塞入标识
isEncrypt: 1
},
data
})
}
import JSEncrypt from 'jsencrypt'
import CryptoJS from 'crypto-js'
//
// 加密
export function rsaEncrypt(Str, afterPublicKey) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(afterPublicKey) // 设置公钥
return encryptor.encrypt(Str) // 对数据进行加密
}
// 解密
export function rsaDecrypt(Str, frontPrivateKey) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(frontPrivateKey) // 设置私钥
return encryptor.decrypt(Str) // 对数据进行解密
}
export function aesEncrypt(aeskey, Str) {
// 设置一个默认值,如果第二个参数为空采用默认值,不为空则采用新设置的密钥
var key = CryptoJS.enc.Utf8.parse(aeskey)
var srcs = CryptoJS.enc.Utf8.parse(Str)
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
// 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.toString()
}
export function aesDecrypt(aeskey, Str) {
var key = CryptoJS.enc.Utf8.parse(aeskey)
var decrypt = CryptoJS.AES.decrypt(Str, key, {
// 切记 需要和后端算法模式一致
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return CryptoJS.enc.Utf8.stringify(decrypt).toString()
}
/**
* 获取16位随机码AES
* @returns {string}
*/
export function get16RandomNum() {
var 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','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'
]
var nums = ''
//这个地方切记要选择16位,因为美国对密钥长度有限制,选择32位的话加解密会报错,需要根据jdk版本去修改相关jar包,有点恼火,选择16位就不用处理。
for (var i = 0; i < 16; i++) {
var id = parseInt(Math.random() * 61)
nums += chars[id]
}
return nums
}
//获取rsa密钥对
export function getRsaKeys() {
return new Promise((resolve, reject) => {
window.crypto.subtle
.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {
name: 'SHA-512' } //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
['encrypt', 'decrypt'] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
)
.then(function(key) {
window.crypto.subtle
.exportKey('pkcs8', key.privateKey)
.then(function(keydata1) {
window.crypto.subtle
.exportKey('spki', key.publicKey)
.then(function(keydata2) {
var privateKey = RSA2text(keydata1, 1)
var publicKey = RSA2text(keydata2)
resolve({
privateKey, publicKey })
})
.catch(function(err) {
reject(err)
})
})
.catch(function(err) {
reject(err)
})
})
.catch(function(err) {
reject(err)
})
})
}
function RSA2text(buffer, isPrivate = 0) {
var binary = ''
var bytes = new Uint8Array(buffer)
var len = bytes.byteLength
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
var base64 = window.btoa(binary)
let text = base64.replace(/[^\x00-\xff]/g, '$&\x01').replace(/.{64}\x01?/g, '$&\n')
return text
}
import Axios from 'axios'
import {
getRsaKeys, rsaEncrypt, rsaDecrypt, aesDecrypt, aesEncrypt, get32RandomNum } from '@common/util'
/**
* axios实例
* @type {AxiosInstance}
*/
const instance = Axios.create({
headers: {
x_requested_with: 'XMLHttpRequest'
}
})
let frontPrivateKey
/**
* axios请求过滤器
*/
instance.interceptors.request.use(
async config => {
if (sessionStorage.getItem('X-Access-Token')) {
// 判断是否存在token,如果存在的话,则每个http header都加上token
config.headers['X-Access-Token'] = sessionStorage.getItem('X-Access-Token')
}
if (config.headers['isEncrypt']) {
config.headers['Content-Type'] = 'application/json;charset=utf-8'
if (config.method === 'post' || config.method === 'put') {
const {
privateKey, publicKey } = await getRsaKeys()
let afterPublicKey = sessionStorage.getItem('afterPublicKey')
frontPrivateKey = privateKey
//每次请求生成aeskey
let aesKey = get16RandomNum()
//用登陆后后端生成并返回给前端的的RSA密钥对的公钥将AES16位密钥进行加密
let aesKeyByRsa = rsaEncrypt(aesKey, afterPublicKey)
//使用AES16位的密钥将请求报文加密(使用的是加密前的aes密钥)
if (config.data) {
let data = aesEncrypt(aesKey, JSON.stringify(config.data))
config.data = {
data: data,
aeskey: aesKeyByRsa,
frontPublicKey: publicKey
}
}
if (config.params) {
let data = aesEncrypt(aesKey, JSON.stringify(config.params))
config.params = {
params: data,
aeskey: aesKeyByRsa,
frontPublicKey: publicKey
}
}
}
}
config.url ="你的后端接口请求地址" + config.url
return config
},
err => {
return Promise.reject(err)
}
)
/**
* axios响应过滤器
*/
instance.interceptors.response.use(
response => {
//后端返回的通过rsa加密后的aes密钥
let aesKeyByRsa = response.data.aesKeyByRsa
if (aesKeyByRsa) {
//通过rsa的私钥对后端返回的加密的aeskey进行解密
let aesKey = rsaDecrypt(aesKeyByRsa, frontPrivateKey)
//使用解密后的aeskey对加密的返回报文进行解密
response.data.data = JSON.parse(JSON.parse(aesDecrypt(aesKey, response.data.data)))
return response.data
}else{
return response
}
}
},
error => {
if (error.response.status === 500) {
const {
data } = erro
更多【状态模式-【前后端接口AES+RSA混合加解密详解(vue+SpringBoot)附完整源码】】相关视频教程:www.yxfzedu.com