简介:
开发中为了数据的安全性使用加密算法对数据进行加密是一种很常见的情况,但就一种语言来说,直接使用提供的相应的库进行少许封装是一件很容易的事。但是在一些情况下我们可能需要跨语言来实现,比如前后端分离的 web 开发中,我们需要前端使用 JS 进行加密与解密,后端则使用 Java、PHP等。这个时候由于不再是使用相同的库,相应的库中提供的默认参数设置也就存在不一样的情况,这个时候要做到前端与后端数据的加密解密交换,就不太容易了。本人也是折腾了两天啃了书,查了许多资料才搞定的。
这里不对原理进行详细赘述,如果还不了解加密算法细节和原理的可以自行查找,本人博客也提供 AES 与 RSA 简单原理介绍,你可以查看。如需详细全面了解,推荐读《Java 加密与解密艺术》这本书。
相关参考:
RSA 加密算法原理简述:https://blog.csdn.net/gulang03/article/details/81176133
AES 加密算法的原理详解:https://blog.csdn.net/gulang03/article/details/81175854
下面的例子是经过测试的,确保了前端与后端都能对相同的数据进行加解密,使用时请尽量与我的环境保持一致。
JDK:1.8.0_201
JS 的相关库及版本,参考源码的注释,由于前端使用了 webpack 打包工具,所以代码中会存在 ES6 语法,如果您使用的是原生 JS,只需参考其中的方法即可。
AES
前端 JS 实现:
/**
* AES 加密算法封装,
* 密钥长度定位 256 位,
* 对外提供密钥的生成、加密、解密
* 本模块测试时使用的是:crypto-js@3.1.9-1,uuid@3.3.2
*/
let CryptJS = require("crypto-js");
let UUID = require("uuid")
export var AESUtil = {
/**
* AES 加密
* @param _content 待加密内容
* @param _key aesKey,
* @param _iv 初始化向量
* @return 返回经 BASE64 处理之后的密文
*/
encrypt: function (_content, _key, _iv) {
// 先以 UTF-8 编码解码参数 返回 any 类型
let content = CryptJS.enc.Utf8.parse(_content);
let aesKey = CryptJS.enc.Utf8.parse(_key);
let iv = CryptJS.enc.Utf8.parse(_iv);
// 加密
let encrypted = CryptJS.AES.encrypt(content, aesKey, {
iv: iv,
mode: CryptJS.mode.CBC,
padding: CryptJS.pad.Pkcs7
})
// console.log(encrypted)
return CryptJS.enc.Base64.stringify(encrypted.ciphertext);
},
/**
* AES 解密
* @param:_content 待解密的内容[Base64处理过的]
* @param:解密用的 AES key
* @param: 初始化向量
* @return 返回以 UTF-8 处理之后的明文
*/
decrypt: function (_content, _key, _iv) {
// let content = CryptJS.enc.Base64.parse(_content);
// content = CryptJS.enc.Base64.stringify(content);
let aesKey = CryptJS.enc.Utf8.parse(_key);
let iv = CryptJS.enc.Utf8.parse(_iv);
// 解密
let decrypted = CryptJS.AES.decrypt(_content, aesKey, {
iv: iv,
mode: CryptJS.mode.CBC,
padding: CryptJS.pad.Pkcs7
})
// console.log(decrypted)
return decrypted.toString(CryptJS.enc.Utf8);
},
/**
* 获得 AES 密钥
* @returns 32 字节的AES密钥
*/
getAesKey: function () {
let uuid = UUID.v1();
// console.log(uuid)
let aeskey = CryptJS.enc.Utf8.parse(uuid)
aeskey = CryptJS.enc.Base64.stringify(aeskey).substring(2, 34)
// console.log(aeskey + "\n" + "长度:" + aeskey.length);
return aeskey;
},
/**
* 获得初始化向量
* @returns 16 字节的初始化向量
*/
getIv: function () {
let uuid = UUID.v1();
let iv = CryptJS.enc.Utf8.parse(uuid);
iv = CryptJS.enc.Base64.stringify(iv).substring(2, 18);
// console.log(iv + "\n" + "长度:" + iv.length);
return iv;
},
/**
* 获得 AES key 及 初始化向量 iv
* 其实 iv 和 aesKey 两者的生成并没有什么关系,两者只是对各自的长度有限制,
* 这里只是为了方便使用,进行了一个组合返回。
* @return 返回 iv 和 aesKey 的组合
*/
getAESKeyAndIv: function () {
let aesKeyAndIv = {
"iv": this.getIv(),
"aesKey": this.getAesKey(),
}
return aesKeyAndIv;
}
}
后端 Java 实现:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.UUID;
/**
* AES 加密方法,是对称的密码算法(加密与解密的密钥一致),这里使用最大的 256 位的密钥,
* 对外提供密钥生成、加密、解密方法
*/
public class AESUtil {
/**
* 获得一个 密钥长度为 8*32 = 256 位的 AES 密钥,
* @return 返回经 BASE64 处理之后的密钥字符串(并截取 32 字节长度)
*/
public static String getAESStrKey() throws NoSuchAlgorithmException, UnsupportedEncodingException {
UUID uuid = UUID.randomUUID();
String aesKey= Base64.getEncoder().encodeToString(uuid.toString().getBytes()).substring(2,34);
return aesKey;
}
/**
* 获得一个初始化向量,初始化向量长度为 4*4 = 16 个字节
* @return 返回经 BASE64 处理之后的密钥字符串(并截取 16 字节长度)
*/
public static String getIv(){
UUID uuid = UUID.randomUUID();
String iv = Base64.getEncoder().encodeToString(uuid.toString().getBytes()).substring(2,18);
return iv;
}
/**
* 获得 AES key 及 初始化向量 iv
* 其实