feat(encrypt): 优化解密逻辑,重构 AES 加解密实现,增加 SM4 密钥校验

This commit is contained in:
PIGCLOUD committed 2026-05-03 19:40:22 +08:00
1 parent 7613f72673
commit 32c6e81b3a
2 files changed
+69 -33

No files matched your search

@@ -23,6 +23,7 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.pig4cloud.pigx.common.api.encrypt.bean.CryptoInfoBean;
import com.pig4cloud.pigx.common.api.encrypt.config.ApiEncryptProperties;
import com.pig4cloud.pigx.common.api.encrypt.enums.EncryptType;
import com.pig4cloud.pigx.common.api.encrypt.util.ApiCryptoUtil;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.RepeatBodyRequestWrapper;
@@ -53,6 +54,9 @@ import static com.pig4cloud.pigx.common.api.encrypt.util.ApiCryptoUtil.KEY_EXTRA
@ConditionalOnProperty(value = ApiEncryptProperties.PREFIX + ".enable", havingValue = "true")
public class DecryptQueryParametersFilter extends OncePerRequestFilter implements Ordered {
/**
* API 加解密配置,提供启用状态、跳过路径和默认加密算法。
*/
private final ApiEncryptProperties encryptProperties;
@@ -69,29 +73,33 @@ public class DecryptQueryParametersFilter extends OncePerRequestFilter implement
return;
}
Map<String, String[]> parameterMap = request.getParameterMap();
if (MapUtil.isEmpty(parameterMap)) {
if (MapUtil.isEmpty(request.getParameterMap())) {
filterChain.doFilter(request, response);
return;
}
// 将请求流转换为可多次读取的请求流
// 包装请求,使 body 与 parameterMap 都可重复读取且可写
RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);
// 构建前端对应解密AES 因子
parameterMap.forEach((k, v) -> {
String[] values = parameterMap.get(k);
// 直接在可写的 wrapper parameterMap 上替换值,避免再做一次 HashMap 拷贝
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
EncryptType encryptType = encryptProperties.getDefaultEncryptType();
String key = KEY_EXTRACTORS.get(encryptType).apply(encryptProperties);
CryptoInfoBean cryptoInfoBean = new CryptoInfoBean(encryptType, key);
// 构建默认算法对应的解密信息,对每个参数的所有值(含多值参数)逐一解密
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String[] values = entry.getValue();
if (ArrayUtil.isEmpty(values)) {
return;
continue;
}
String key = KEY_EXTRACTORS.get(encryptProperties.getDefaultEncryptType()).apply(encryptProperties);
CryptoInfoBean cryptoInfoBean = new CryptoInfoBean(encryptProperties.getDefaultEncryptType(), key);
// 解密并覆盖原有值
String decryptData = ApiCryptoUtil.decryptData(URLUtil.decode(values[0]), cryptoInfoBean);
parameterMap.put(k, new String[]{decryptData});
});
String[] decryptedValues = new String[values.length];
for (int i = 0; i < values.length; i++) {
decryptedValues[i] = ApiCryptoUtil.decryptData(URLUtil.decode(values[i]), cryptoInfoBean);
}
entry.setValue(decryptedValues);
}
requestWrapper.setParameterMap(parameterMap);
filterChain.doFilter(requestWrapper, response);
}
@@ -21,7 +21,6 @@ import org.springframework.util.Assert;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Function;
@@ -38,6 +37,11 @@ import java.util.function.Function;
@UtilityClass
public class ApiCryptoUtil {
/**
* SM4 密钥 HEX 字符串长度,对应 16 字节(128 bit)原始密钥。
*/
private static final int SM4_HEX_KEY_LENGTH = 32;
/**
* 密钥提取器
*/
@@ -73,11 +77,7 @@ public class ApiCryptoUtil {
return SecureUtil.des(secretKey.getBytes(StandardCharsets.UTF_8)).encryptBase64(jsonData);
}
if (type == EncryptType.AES) {
// 构建前端对应解密AES 因子
AES aes = new AES(Mode.CFB, Padding.NoPadding, new SecretKeySpec(secretKey.getBytes(), "AES"),
new IvParameterSpec(secretKey.getBytes()));
return aes.encryptBase64(jsonData);
return buildAes(secretKey).encryptBase64(jsonData);
}
if (type == EncryptType.RSA) {
return SecureUtil.rsa(secretKey.getBytes(StandardCharsets.UTF_8), null)
@@ -85,7 +85,7 @@ public class ApiCryptoUtil {
}
if (type == EncryptType.SM4) {
return SmUtil.sm4(HexUtil.decodeHex(secretKey)).encryptHex(jsonData);
return SmUtil.sm4(getSm4KeyBytes(secretKey)).encryptHex(jsonData);
}
throw new EncryptBodyFailException();
}
@@ -93,9 +93,9 @@ public class ApiCryptoUtil {
/**
* 选择加密方式并进行解密
*
* @param bodyData byte array
* @param infoBean 加密信息
* @return 解密结果
* @param bodyData 密文字节数据
* @param infoBean 加密类型和密钥信息
* @return 解密后的原始字节数据
*/
public static byte[] decryptData(byte[] bodyData, CryptoInfoBean infoBean) {
EncryptType type = infoBean.getType();
@@ -110,12 +110,7 @@ public class ApiCryptoUtil {
Assert.hasText(secretKey, type + " key is not configured (未配置" + type + ")");
if (type == EncryptType.AES) {
AES aes = new AES(Mode.CFB, Padding.NoPadding,
new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES"),
new IvParameterSpec(secretKey.getBytes(StandardCharsets.UTF_8)));
return aes.decrypt(StrUtil.str(bodyData, StandardCharsets.UTF_8));
return buildAes(secretKey).decrypt(StrUtil.str(bodyData, StandardCharsets.UTF_8));
}
if (type == EncryptType.DES) {
return SecureUtil.des(secretKey.getBytes(StandardCharsets.UTF_8)).decrypt(bodyData);
@@ -126,16 +121,49 @@ public class ApiCryptoUtil {
}
if (type == EncryptType.SM4) {
return SmUtil.sm4(HexUtil.decodeHex(secretKey))
.decryptStr(StrUtil.str(bodyData, Charset.defaultCharset()))
.getBytes();
return SmUtil.sm4(getSm4KeyBytes(secretKey))
.decryptStr(StrUtil.str(bodyData, StandardCharsets.UTF_8))
.getBytes(StandardCharsets.UTF_8);
}
throw new EncryptMethodNotFoundException();
}
/**
* 解密字符串形式的密文数据。
*
* @param bodyData 字符串密文,按 UTF-8 转换为字节数组后解密
* @param infoBean 加密类型和密钥信息
* @return UTF-8 字符串形式的解密结果
*/
public static String decryptData(String bodyData, CryptoInfoBean infoBean) {
return StrUtil.str(decryptData(bodyData.getBytes(StandardCharsets.UTF_8), infoBean), StandardCharsets.UTF_8);
}
/**
* 构建与前端约定的 AES 加解密因子:CFB 模式 + NoPadding,
* 同时使用密钥的 UTF-8 字节作为 Key 与 IV,避免平台默认字符集差异。
*
* @param secretKey AES 密钥字符串
* @return 已配置好 Key 与 IV 的 AES 实例
*/
private static AES buildAes(String secretKey) {
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
return new AES(Mode.CFB, Padding.NoPadding,
new SecretKeySpec(keyBytes, "AES"),
new IvParameterSpec(keyBytes));
}
/**
* 校验并解析 SM4 HEX 密钥。
*
* @param secretKey SM4 密钥,必须是 32 位 HEX 字符串(对应 16 字节 / 128 bit)
* @return SM4 原始密钥字节
*/
private static byte[] getSm4KeyBytes(String secretKey) {
Assert.isTrue(secretKey.length() == SM4_HEX_KEY_LENGTH && HexUtil.isHexNumber(secretKey),
EncryptType.SM4 + " key must be 32 hex characters (SM4密钥必须为32位HEX字符串)");
return HexUtil.decodeHex(secretKey);
}
}