mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 12:39:05 +08:00
feat(encrypt): 优化解密逻辑,重构 AES 加解密实现,增加 SM4 密钥校验
This commit is contained in:
1 parent
7613f72673
commit
32c6e81b3a
2 files changed
+69
-33
No files matched your search
+22
-14
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+47
-19
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user