From 32c6e81b3a43ee3c47df2b6b57a376bcb3a159dc Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Sun, 3 May 2026 19:40:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(encrypt):=20=E4=BC=98=E5=8C=96=E8=A7=A3?= =?UTF-8?q?=E5=AF=86=E9=80=BB=E8=BE=91=EF=BC=8C=E9=87=8D=E6=9E=84=20AES=20?= =?UTF-8?q?=E5=8A=A0=E8=A7=A3=E5=AF=86=E5=AE=9E=E7=8E=B0=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20SM4=20=E5=AF=86=E9=92=A5=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/DecryptQueryParametersFilter.java | 36 ++++++---- .../api/encrypt/util/ApiCryptoUtil.java | 66 +++++++++++++------ 2 files changed, 69 insertions(+), 33 deletions(-) diff --git a/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/core/DecryptQueryParametersFilter.java b/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/core/DecryptQueryParametersFilter.java index ca3e2bf55..c5bc909db 100644 --- a/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/core/DecryptQueryParametersFilter.java +++ b/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/core/DecryptQueryParametersFilter.java @@ -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 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 parameterMap = requestWrapper.getParameterMap(); + EncryptType encryptType = encryptProperties.getDefaultEncryptType(); + String key = KEY_EXTRACTORS.get(encryptType).apply(encryptProperties); + CryptoInfoBean cryptoInfoBean = new CryptoInfoBean(encryptType, key); + + // 构建默认算法对应的解密信息,对每个参数的所有值(含多值参数)逐一解密 + for (Map.Entry 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); } diff --git a/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/util/ApiCryptoUtil.java b/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/util/ApiCryptoUtil.java index a0883ee7b..b648ea818 100644 --- a/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/util/ApiCryptoUtil.java +++ b/pigx-common/pigx-common-encrypt-api/src/main/java/com/pig4cloud/pigx/common/api/encrypt/util/ApiCryptoUtil.java @@ -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); + } + }