ruoyi-vue-plus 数据加密传输
技术需求
在前后端应用开发中,有些敏感数据如密码、个人隐私等信息如果以明文的方式传输,存在信息泄露和被篡改风险。对于敏感数据需要在前端先加密在进行传输。
加密原理
加密方法
目前常见的加密方式有两种,一种是对称加密(AES为代表),一种是非对称加密(RSA为代表)。
对称加密只有一个秘钥,加密和解密都是用同一个秘钥,所以叫做对称加密。
非对称加密有两个秘钥,一个是公钥,一个是私钥。非对称的特点在于,公钥加密的私钥可以解密,但私钥加密的,公钥解不出来,只能验证是否由私钥进行加密。这样可以保证就算有人拿到公钥,也解密不出私钥加密后的信息,公钥可以在网上安全的传输。而且公钥可以验证这个密文是不是由私钥加密出来的,也起到了验证的作用,一举两得。
一般公钥加密私钥解密的情况是用来传输数据,私钥加密公钥验证的情况是用来验证签名。
组合使用
对称加密(AES)的优势在于加密较快,但劣势在于秘钥一旦给出去就不安全了。非对称加密(RSA)的优势在于安全,就算提供公钥出去,别人也解密不了数据,但加密速度较慢。实际使用的过程中常常将两者组合使用(AES+RSA):
1、先生成一个随机AES秘钥字符串。
2、使用RSA公钥加密AES秘钥,然后再用AES秘钥加密真正的内容。
3、把skey=加密的AES秘钥,body=AES秘钥加密的内容传过去。
4、对面使用RSA私钥解密AES秘钥,然后用AES秘钥解密出内容。
这样可以安全的传输AES秘钥,避免了RSA加密的慢速度。
代码实现
前端加密
javascript
// 当需要加密时:
// 生成一个 AES 密钥
const aesKey = generateAesKey();
// 加密AES密钥(RAS加密),存放在header中,传递给后端
config.headers[encryptHeader] = encrypt(encryptBase64(aesKey));
// 用AES密钥对内容进行加密
config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey);
前端加密主要引用crypto-js 和 jsencrypt 类库
- 使用 JSEncrypt 非对称加密(RSA)
javascript
import JSEncrypt from 'jsencrypt';
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;
// 前端不建议存放私钥 不建议解密数据 因为都是透明的意义不大
const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY;
// 加密
export const encrypt = (txt: string) => {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey); // 设置公钥
return encryptor.encrypt(txt); // 对数据进行加密
};
// 解密
export const decrypt = (txt: string) => {
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(privateKey); // 设置私钥
return encryptor.decrypt(txt); // 对数据进行解密
};
- 使用CryptoJS对称加密(AES)
javascript
import CryptoJS from 'crypto-js';
/**
* 使用密钥对数据进行加密
* @param message
* @param aesKey
* @returns {string}
*/
export const encryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
};
后端解密
java
// 获取 AES 密钥
String headerRsa = request.getHeader(headerFlag);
// 解密 AES 密钥 (RAS解密)
String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
// 获取 加密内容
request.setCharacterEncoding(Constants.UTF8);
byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
String requestBody = new String(readBytes, StandardCharsets.UTF_8);
// 解密内容 (用AES密钥解密)
String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
后端解密主要由hutool-crypto 实现
java
import cn.hutool.crypto.SecureUtil;
/**
* AES解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
*/
public static String decryptByAes(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// aes算法的秘钥要求是16位、24位、32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
参考资料
https://blog.csdn.net/Michelle_Zhong/article/details/134018557