一、流程:
① 前端使用AES加密数据,并将AES KEY做RSA加密,最后用SM4加密数据结构并发送到后端
② 后端使用约定好的SM4加密的种子,解密出请求数据结构;使用RSA私钥解密出AES KEY,并用AES KEY + 约定好的盐值获取方式,解密出数据体,最后保存AES KEY到ThreadLocal中
③ 后端准备响应数据,从ThreadLocal中获取AES KEY,利用前后端约定好的盐值获取方式,加密响应数据
④ 前端获取到响应数据,利用之前生成的AES KEY和盐,解密出响应数据
二、代码:
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SM4;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.RandomStringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 演示前后端加解密过程
*/
public class TestEncryptionAndDecryption {
private static final String RSA_PUBLIC_KEY;
private static final String RSA_PRIVATE_KEY;
private static final DateTimeFormatter YYYYMMDDHHMMSS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final DateTimeFormatter YYYYMMDD = DateTimeFormatter.ofPattern("yyyyMMdd");
static {
RSA rsa = new RSA();
RSA_PUBLIC_KEY = rsa.getPublicKeyBase64();
RSA_PRIVATE_KEY = rsa.getPrivateKeyBase64();
}
public static void main(String[] args) {
// 前端将数据发送到服务器 ==============================>
// 模拟前端加密
String encryptedRequestStr = testFrontEncryption(REQUEST_DATA);
System.out.println("前端加密后的请求内容:\n" + encryptedRequestStr);
System.out.println();
// 模拟后端解密
Map<String, String> result = testBackendDecryption(encryptedRequestStr);
System.out.println("后端解密后的请求内容:\n" + result.get("decryptedRequestStr"));
System.out.println();
// 服务器返回加密数据 ==============================>
// 模拟后端加密响应数据
String encrptedResponseStr = testBackendEncryption(RESPONSE_DATA, result.get("aesKeyStr"));
System.out.println("后端加密后的响应内容:\n" + encrptedResponseStr);
System.out.println();
// 模拟前端解密响应数据
String decrptedResponseStr = testFrontDecrption(encrptedResponseStr, result.get("aesKeyStr"));
System.out.println("前端解密后的响应内容:\n" + decrptedResponseStr);
}
// 模拟前端解密AES加密后的数据
// 流程
// 1、前端发出请求的时候,生成了AES KEY 和 IV
// 2、后端又是使用前端生成的AES KEY + IV 加密的响应数据
// 3、因此,前端拿到响应数据后,可以直接做解密获得响应数据
static String testFrontDecrption(String cipherStr, String aesKeyStr) {
// 根据 aesKeyStr 生成偏移量iv
byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKeyStr, YYYYMMDD.format(LocalDate.now()))));
// aesKeyStr是base64编码的,下面解码为字节数组
byte[] aesKey = Base64.decode(aesKeyStr.getBytes(CharsetUtil.CHARSET_UTF_8));
// 响应数据做AES解密
AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKey, iv);
// 获取AES解码后的响应数据
return aes.decryptStr(cipherStr);
}
// 后端加密响应数据
// 请求和响应是一套完整的流程,在请求中,通过RSA解密得到的AES KEY,可以通过某种方式传递到响应返回的时候使用,给响应数据做加密
static String testBackendEncryption(String plainStr, String aesKeyStr) {
// 根据 aesKeyStr 生成偏移量iv
byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKeyStr, YYYYMMDD.format(LocalDate.now()))));
// aesKeyStr是base64编码的,下面解码为字节数组
byte[] aesKey = Base64.decode(aesKeyStr.getBytes(CharsetUtil.CHARSET_UTF_8));
// 响应数据做AES加密
AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKey, iv);
// 返回Base64编码的响应数据
return aes.encryptBase64(plainStr);
}
// 前端加密数据请求服务器
// 流程:
// 1、生成一个随机的AES KEY
// 2、获取AES KEY的盐(前后端获取盐的方式应该一致,这里是利用了当前日期的字符串)
// 3、请求数据使用 AES KEY 和 盐 做AES对称加密
// 4、为了可以解密响应的加密数据,前端将AES KEY使用RSA非对称加密后传给后端,这样后端可以使用这个AES KEY 和约定的盐,对响应数据加密,前端可以用相同KEY和IV解密响应数据
// 5、为了隐藏请求数据的结构,前端使用SM4国密算法,利用当前日期做种子,加密请求数据结构并向后端发送请求
static String testFrontEncryption(String plainStr) {
// 使用 UUID + 当前时间字符串 + 32位随机字符串,生成一个 AES 密钥
String dateTimeStr = YYYYMMDDHHMMSS.format(LocalDateTime.now());
String randomStr = RandomStringUtils.random(32, true, true);
byte[] aesKey = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}-{}-{}", UUID.randomUUID().toString(), dateTimeStr, randomStr)));
// 使用当前日期的字符串,生成 AES密码 的偏移量(盐) iv
String base64AesKey = Base64.encode(aesKey);
String dateStr = YYYYMMDD.format(LocalDate.now());
byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", base64AesKey, dateStr)));
// 对内容做AES加密
AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKey, iv);
String data = aes.encryptHex(plainStr);
// 使用前后端约定好的公钥,对AES密钥做RSA加密
RSA rsa = new RSA(RSA_PRIVATE_KEY, RSA_PUBLIC_KEY);
String key = rsa.encryptHex(base64AesKey, KeyType.PublicKey);
// 组装请求数据
Map<String, String> reqDataObj = new HashMap<>();
reqDataObj.put("key", key);
reqDataObj.put("data", data);
// 对请求数据的结构做简单的SM4加密
String sm4Key = SecureUtil.md5(dateStr);
SM4 sm4 = new SM4(Mode.ECB, Padding.PKCS5Padding, HexUtil.decodeHex(sm4Key));
return sm4.encryptHex(JSON.toJSONString(reqDataObj));
}
// 后端解密前端的请求数据
// 流程
// 1、其实就是前端加密过程的逆向
// 2、首先做SM4解密,由于前后端约定使用当前日期做种子加解密请求数据结构,因此,首先做SM4解密,得到请求数据结构体
// 3、前端对AES KEY做了RSA加密,公钥是前后端约定好的,因此,后端使用私钥直接做RSA解密,得到AES KEY
// 4、用前后端约定好的方式,通过当前日期,获得AES KEY的IV
// 5、使用AES KEY 和 IV 解密出请求数据
// 6、后端保存AES KEY,以在返回的时候加密响应数据
static Map<String, String> testBackendDecryption(String cipherStr) {
// 将SM4加密过的数据,用同样的KEY做解密,这个KEY就是当前日期的字符串做MD5加密后的值
String dateStr = YYYYMMDD.format(LocalDate.now());
String sm4Key = SecureUtil.md5(dateStr);
SM4 sm4 = new SM4(Mode.ECB, Padding.PKCS5Padding, HexUtil.decodeHex(sm4Key));
// 得到SM4解密后的数据,即reqDataObj的JSON串
String decryptStr = sm4.decryptStr(cipherStr);
// 由于前后端约定,数据传输使用JSON,因此,将做了SM4解密后的数据转换成JSONObject
JSONObject reqDataObj = JSON.parseObject(decryptStr);
// 得到RSA加密后的KEY
String key = reqDataObj.getString("key");
// 得到AES加密后的VALUE
String data = reqDataObj.getString("data");
// 使用私钥解密出RSA加密后的AES KEY
RSA rsa = new RSA(RSA_PRIVATE_KEY, RSA_PUBLIC_KEY);
// 得到可以解密AES加密后的VALUE的KEY
String aesKey = rsa.decryptStr(key, KeyType.PrivateKey, CharsetUtil.CHARSET_UTF_8);
// 得到偏移量(盐值)
byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKey, dateStr)));
// 得到Base64解码后的KEY
byte[] aesKeyByte = Base64.decode(aesKey);
// 使用盐值 和 KEY,构造AES对象
AES aes = new AES(Mode.CFB, Padding.PKCS5Padding, aesKeyByte, iv);
// 得到AES解密后的值
String decryptedRequestStr = aes.decryptStr(data);
Map<String, String> result = new HashMap<>();
result.put("aesKeyStr", aesKey);
result.put("decryptedRequestStr", decryptedRequestStr);
return result;
}
// 请求数据
private final static String REQUEST_DATA = "{\n" +
" \"key_1\" : \"value_1\",\n" +
" \"key_2\" : \"value_2\",\n" +
" \"key_3\" : \"value_3\",\n" +
" \"key_4\" : \"value_4\",\n" +
" \"key_5\" : \"value_5\",\n" +
" \"key_6\" : \"value_6\",\n" +
" \"key_7\" : \"value_7\"\n" +
"}";
// 响应数据
private final static String RESPONSE_DATA = "{\n" +
" \"key_1\": \"value_1\",\n" +
" \"key_2\": \"value_2\",\n" +
" \"key_3\": \"value_3\",\n" +
" \"key_4\": {\n" +
" \"key_4_1\": \"value_4_1\",\n" +
" \"key_4_2\": \"value_4_2\",\n" +
" \"key_4_3\": \"value_4_3\",\n" +
" \"key_4_4\": \"value_4_4\"\n" +
" }\n" +
"}";
}