diff --git a/_config/sql/update-transfer-sensitive-encrypt.sql b/_config/sql/update-transfer-sensitive-encrypt.sql new file mode 100644 index 000000000..29084e974 --- /dev/null +++ b/_config/sql/update-transfer-sensitive-encrypt.sql @@ -0,0 +1,64 @@ +-- ===================================================================== +-- 转账收款人敏感字段加密迁移检查与说明 +-- ===================================================================== +-- 背景: pay_transfer_order_{alipay,douyin,wechat} 三表的收款人账号/姓名字段 +-- 已接入 DataEncryptTypeHandler (AES-256-GCM), 密文格式 v{version}:{base64}。 +-- +-- SecureAesGcmEncryptor.decrypt 已做容错: 非密文格式(历史明文)原样透传返回, +-- 不会变 null, 故上线零风险, 历史数据可正常读取。迁移仅用于"把明文也加密落库", +-- 属合规优化, 非上线阻塞项。 +-- +-- 迁移方式: AES-GCM 加密需随机 IV + 密钥版本前缀, 纯 SQL 无法完成, +-- 必须通过应用层(MyBatis updateById)触发 TypeHandler 加密写入。 +-- 迁移完成后, 下方检查语句查询结果应均为 0。 +-- ===================================================================== + +-- 1. 检查历史明文数量(未加密 = 不以 v 开头且非空) + +-- 支付宝转账单: 收款人账号 + 收款人姓名 +SELECT 'pay_transfer_order_alipay.payee_account' AS field, + count(*) AS plaintext_count +FROM pay_transfer_order_alipay +WHERE payee_account IS NOT NULL AND payee_account NOT LIKE 'v%' +UNION ALL +SELECT 'pay_transfer_order_alipay.payee_name', + count(*) +FROM pay_transfer_order_alipay +WHERE payee_name IS NOT NULL AND payee_name NOT LIKE 'v%'; + +-- 抖音转账单: 收款人账号 + 收款人姓名 +SELECT 'pay_transfer_order_douyin.payee_account' AS field, + count(*) AS plaintext_count +FROM pay_transfer_order_douyin +WHERE payee_account IS NOT NULL AND payee_account NOT LIKE 'v%' +UNION ALL +SELECT 'pay_transfer_order_douyin.payee_name', + count(*) +FROM pay_transfer_order_douyin +WHERE payee_name IS NOT NULL AND payee_name NOT LIKE 'v%'; + +-- 微信转账单: 收款人 openid + 收款人姓名(userName) +SELECT 'pay_transfer_order_wechat.payee_openid' AS field, + count(*) AS plaintext_count +FROM pay_transfer_order_wechat +WHERE payee_openid IS NOT NULL AND payee_openid NOT LIKE 'v%' +UNION ALL +SELECT 'pay_transfer_order_wechat.user_name', + count(*) +FROM pay_transfer_order_wechat +WHERE user_name IS NOT NULL AND user_name NOT LIKE 'v%'; + +-- 2. 迁移方法(应用层一次性执行) +-- +-- 启用加密(daxpay.platform.config.encrypt.enable=true + 配置 keys)后, +-- 遍历历史记录执行 updateById 即可触发 TypeHandler 加密写入: +-- +-- // 伪代码示例(三表同理) +-- alipayTransferOrderManager.list().forEach(e -> { +-- e.setPayeeAccount(e.getPayeeAccount()); // 自身赋值触发 dirty +-- e.setPayeeName(e.getPayeeName()); +-- alipayTransferOrderManager.updateById(e); // TypeHandler 写入密文 +-- }); +-- +-- 可封装为 CommandLineRunner / 一次性 admin 接口, 随 profile 触发, 执行后下线。 +-- 迁移完成后重新执行上方检查语句, plaintext_count 应全部为 0。 diff --git a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/TransferAdminService.java b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/TransferAdminService.java index d09e1e0d9..ae28cde14 100644 --- a/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/TransferAdminService.java +++ b/daxpay-payment/daxpay-payment-admin/src/main/java/cn/daxpay/open/payment/admin/service/trade/TransferAdminService.java @@ -24,6 +24,7 @@ import cn.daxpay.open.payment.trade.transfer.result.WechatTransferOrderResult; import cn.daxpay.open.payment.trade.transfer.runtime.service.TransferCloseService; import cn.daxpay.open.payment.trade.transfer.runtime.service.TransferStartService; import cn.daxpay.open.payment.trade.transfer.runtime.service.TransferSyncService; +import cn.daxpay.open.payment.trade.transfer.util.PayeeDesensitizeUtil; import cn.daxpay.open.platform.common.translate.service.TransService; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.core.rest.param.PageParam; @@ -56,6 +57,8 @@ public class TransferAdminService { Page page = wechatTransferOrderManager.page(pageParam, query); PageResult pageResult = toPageResult(page, WechatTransferOrderConvert.CONVERT::toResult); transService.translate(pageResult); + // 收款人信息脱敏 + pageResult.getRecords().forEach(PayeeDesensitizeUtil::desensitize); return pageResult; } @@ -65,6 +68,8 @@ public class TransferAdminService { .orElseThrow(() -> new DataNotExistException("pay.error.transfer.notFound")); WechatTransferOrderResult result = WechatTransferOrderConvert.CONVERT.toResult(entity); transService.translate(result); + // 收款人信息脱敏 + PayeeDesensitizeUtil.desensitize(result); return result; } @@ -75,6 +80,8 @@ public class TransferAdminService { Page page = alipayTransferOrderManager.page(pageParam, query); PageResult pageResult = toPageResult(page, AlipayTransferOrderConvert.CONVERT::toResult); transService.translate(pageResult); + // 收款人信息脱敏 + pageResult.getRecords().forEach(PayeeDesensitizeUtil::desensitize); return pageResult; } @@ -84,6 +91,8 @@ public class TransferAdminService { .orElseThrow(() -> new DataNotExistException("pay.error.transfer.notFound")); AlipayTransferOrderResult result = AlipayTransferOrderConvert.CONVERT.toResult(entity); transService.translate(result); + // 收款人信息脱敏 + PayeeDesensitizeUtil.desensitize(result); return result; } @@ -94,6 +103,8 @@ public class TransferAdminService { Page page = douyinTransferOrderManager.page(pageParam, query); PageResult pageResult = toPageResult(page, DouyinTransferOrderConvert.CONVERT::toResult); transService.translate(pageResult); + // 收款人信息脱敏 + pageResult.getRecords().forEach(PayeeDesensitizeUtil::desensitize); return pageResult; } @@ -103,6 +114,8 @@ public class TransferAdminService { .orElseThrow(() -> new DataNotExistException("pay.error.transfer.notFound")); DouyinTransferOrderResult result = DouyinTransferOrderConvert.CONVERT.toResult(entity); transService.translate(result); + // 收款人信息脱敏 + PayeeDesensitizeUtil.desensitize(result); return result; } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/AlipayTransferOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/AlipayTransferOrder.java index 8732635dc..4e2d9b8de 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/AlipayTransferOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/AlipayTransferOrder.java @@ -1,6 +1,7 @@ package cn.daxpay.open.payment.trade.transfer.entity; import cn.daxpay.open.payment.common.entity.MchBaseEntity; +import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler; import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -22,7 +23,7 @@ import java.time.OffsetDateTime; @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -@TableName("pay_transfer_order_alipay") +@TableName(value = "pay_transfer_order_alipay", autoResultMap = true) public class AlipayTransferOrder extends MchBaseEntity { // ===== 身份 ===== @@ -77,10 +78,12 @@ public class AlipayTransferOrder extends MchBaseEntity { /// @see cn.daxpay.open.payment.trade.transfer.enums.TransferPayeeTypeEnum private String payeeType; - /// 收款人账号 + /// 收款人账号(敏感字段, AES-256-GCM 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) private String payeeAccount; - /// 收款人姓名 + /// 收款人姓名(敏感字段, AES-256-GCM 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) private String payeeName; /// 转账场景标识(支付宝=转账场景配置ID, FAIL重试时恢复场景用) diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/DouyinTransferOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/DouyinTransferOrder.java index 090836f75..433b76067 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/DouyinTransferOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/DouyinTransferOrder.java @@ -1,6 +1,7 @@ package cn.daxpay.open.payment.trade.transfer.entity; import cn.daxpay.open.payment.common.entity.MchBaseEntity; +import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler; import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -22,7 +23,7 @@ import java.time.OffsetDateTime; @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -@TableName("pay_transfer_order_douyin") +@TableName(value = "pay_transfer_order_douyin", autoResultMap = true) public class DouyinTransferOrder extends MchBaseEntity { // ===== 身份 ===== @@ -73,10 +74,12 @@ public class DouyinTransferOrder extends MchBaseEntity { /// 收款人账号类型 private String payeeType; - /// 收款人账号 + /// 收款人账号(敏感字段, AES-256-GCM 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) private String payeeAccount; - /// 收款人姓名 + /// 收款人姓名(敏感字段, AES-256-GCM 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) private String payeeName; /// 转账场景ID(发起转账时由前端选择的主数据枚举, 如1001-1007) diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java index f4c0aa1fd..8cb3482a6 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java @@ -1,6 +1,7 @@ package cn.daxpay.open.payment.trade.transfer.entity; import cn.daxpay.open.payment.common.entity.MchBaseEntity; +import cn.daxpay.open.platform.common.mybatisplus.handler.encrypt.DataEncryptTypeHandler; import com.baomidou.mybatisplus.annotation.FieldStrategy; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -22,7 +23,7 @@ import java.time.OffsetDateTime; @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -@TableName("pay_transfer_order_wechat") +@TableName(value = "pay_transfer_order_wechat", autoResultMap = true) public class WechatTransferOrder extends MchBaseEntity { // ===== 身份 ===== @@ -70,7 +71,8 @@ public class WechatTransferOrder extends MchBaseEntity { // ===== 微信特有 ===== - /// 收款人微信 openid + /// 收款人微信 openid(敏感字段, AES-256-GCM 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) private String payeeOpenid; /// 转账场景(冗余自转账配置) @@ -82,7 +84,8 @@ public class WechatTransferOrder extends MchBaseEntity { /// 拉起转账确认参数(微信二次确认) private String transferBody; - /// 收款人姓名(金额档位校验用: 小于0.3元禁填, 大于等于2000元必填) + /// 收款人姓名(金额档位校验用: 小于0.3元禁填, 大于等于2000元必填; 敏感字段, AES-256-GCM 加密存储) + @TableField(typeHandler = DataEncryptTypeHandler.class) private String userName; // ===== 商户出站 / 审计 ===== diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/util/PayeeDesensitizeUtil.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/util/PayeeDesensitizeUtil.java new file mode 100644 index 000000000..7982cd8c4 --- /dev/null +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/util/PayeeDesensitizeUtil.java @@ -0,0 +1,133 @@ +package cn.daxpay.open.payment.trade.transfer.util; + +import cn.daxpay.open.payment.trade.transfer.enums.TransferPayeeTypeEnum; +import cn.daxpay.open.payment.trade.transfer.result.AlipayTransferOrderResult; +import cn.daxpay.open.payment.trade.transfer.result.DouyinTransferOrderResult; +import cn.daxpay.open.payment.trade.transfer.result.WechatTransferOrderResult; +import cn.hutool.core.util.StrUtil; + +/// # 收款人信息脱敏工具 +/// +/// 用于转账单查询/详情返回时对收款人姓名、收款账号进行展示脱敏(底层存储已加密)。 +/// 仅作用于 Result 出口, 不影响实体上送通道(通道策略读到的仍是解密后的明文)。 +/// +/// 脱敏策略: +/// - **姓名**: 两字保留首字, 三字及以上保留首尾, 中间用 `*` 占位 +/// - **收款账号**: 按 [TransferPayeeTypeEnum] 分级 +/// - 手机号(`phone` / `login_name` 为手机号): `138****5678`(前3后4) +/// - 邮箱(`login_name` 为邮箱): `z***@example.com`(首字符 + 域名) +/// - `openid` / `user_id` / `open_id`: 伪匿名标识, 原样返回(运维排查需可见) +public final class PayeeDesensitizeUtil { + + private PayeeDesensitizeUtil() { + } + + /// 姓名脱敏: 两字保留首字, 三字及以上保留首尾, 中间用 * 占位 + public static String maskName(String name) { + if (StrUtil.isBlank(name)) { + return name; + } + int len = name.length(); + if (len == 1) { + return name; + } + if (len == 2) { + // 张三 -> 张* + return name.charAt(0) + "*"; + } + // 张三丰 -> 张*丰; 诸葛亮 -> 诸**亮 + return name.charAt(0) + "*".repeat(len - 2) + name.charAt(len - 1); + } + + /// 收款账号脱敏: 按收款人类型分级处理 + /// + /// @param account 收款账号明文(实体层已由 [DataEncryptTypeHandler] 解密) + /// @param payeeType 收款人账号类型, @see TransferPayeeTypeEnum + public static String maskAccount(String account, String payeeType) { + if (StrUtil.isBlank(account)) { + return account; + } + // 仅对真个人信息(手机号/邮箱)脱敏; openid/user_id/open_id 为伪匿名标识, 原样返回 + if (!isSensitiveAccount(payeeType)) { + return account; + } + // login_name 可能是手机号或邮箱, 按内容自动判断 + if (isEmail(account)) { + return maskEmail(account); + } + if (isMobile(account)) { + return maskMobile(account); + } + // 兜底: 保留首尾各1字符 + return maskBothEnds(account); + } + + /// 判断该收款人类型的账号是否需要脱敏(手机号/邮箱为个人信息, 需脱敏) + private static boolean isSensitiveAccount(String payeeType) { + return TransferPayeeTypeEnum.LOGIN_NAME.getCode().equals(payeeType) + || TransferPayeeTypeEnum.PHONE.getCode().equals(payeeType); + } + + /// 简单邮箱判断: 包含 @ 且 @ 不在首尾 + private static boolean isEmail(String value) { + int at = value.indexOf('@'); + return at > 0 && at < value.length() - 1; + } + + /// 简单手机号判断: 11 位纯数字(国内) + private static boolean isMobile(String value) { + return value.length() == 11 && value.chars().allMatch(Character::isDigit); + } + + /// 手机号脱敏: 138****5678(前3后4) + private static String maskMobile(String mobile) { + return mobile.substring(0, 3) + "****" + mobile.substring(7); + } + + /// 邮箱脱敏: z***@example.com(本地名首字符 + 3星 + 域名) + private static String maskEmail(String email) { + int at = email.indexOf('@'); + String local = email.substring(0, at); + String domain = email.substring(at); + if (local.length() <= 1) { + return "*" + domain; + } + return local.charAt(0) + "***" + domain; + } + + /// 兜底脱敏: 保留首尾各1字符, 中间用 * 占位 + private static String maskBothEnds(String value) { + if (value.length() <= 2) { + return "*".repeat(value.length()); + } + return value.charAt(0) + "*".repeat(value.length() - 2) + value.charAt(value.length() - 1); + } + + // ===== Result 级脱敏入口(供 Admin/Merchant Service 出口统一调用) ===== + + /// 支付宝转账单收款人脱敏: 姓名 + 收款账号(按类型) + public static void desensitize(AlipayTransferOrderResult result) { + if (result == null) { + return; + } + result.setPayeeName(maskName(result.getPayeeName())); + result.setPayeeAccount(maskAccount(result.getPayeeAccount(), result.getPayeeType())); + } + + /// 抖音转账单收款人脱敏: 姓名 + 收款账号(按类型) + public static void desensitize(DouyinTransferOrderResult result) { + if (result == null) { + return; + } + result.setPayeeName(maskName(result.getPayeeName())); + result.setPayeeAccount(maskAccount(result.getPayeeAccount(), result.getPayeeType())); + } + + /// 微信转账单收款人脱敏: 仅姓名(openid 为伪匿名标识, 不脱敏) + public static void desensitize(WechatTransferOrderResult result) { + if (result == null) { + return; + } + result.setUserName(maskName(result.getUserName())); + } +} diff --git a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchTransferService.java b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchTransferService.java index 6d608d1c4..3c7688229 100644 --- a/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchTransferService.java +++ b/daxpay-payment/daxpay-payment-merchant/src/main/java/cn/daxpay/open/payment/merchant/service/trade/MchTransferService.java @@ -24,6 +24,7 @@ import cn.daxpay.open.payment.trade.transfer.result.WechatTransferOrderResult; import cn.daxpay.open.payment.trade.transfer.runtime.service.TransferCloseService; import cn.daxpay.open.payment.trade.transfer.runtime.service.TransferStartService; import cn.daxpay.open.payment.trade.transfer.runtime.service.TransferSyncService; +import cn.daxpay.open.payment.trade.transfer.util.PayeeDesensitizeUtil; import cn.daxpay.open.platform.common.translate.service.TransService; import cn.daxpay.open.platform.core.exception.DataNotExistException; import cn.daxpay.open.platform.core.rest.param.PageParam; @@ -53,6 +54,8 @@ public class MchTransferService { Page page = wechatTransferOrderManager.page(pageParam, query); PageResult pageResult = toPageResult(page, WechatTransferOrderConvert.CONVERT::toResult); transService.translate(pageResult); + // 收款人信息脱敏 + pageResult.getRecords().forEach(PayeeDesensitizeUtil::desensitize); return pageResult; } @@ -62,6 +65,8 @@ public class MchTransferService { .orElseThrow(() -> new DataNotExistException("pay.error.transfer.notFound")); WechatTransferOrderResult result = WechatTransferOrderConvert.CONVERT.toResult(entity); transService.translate(result); + // 收款人信息脱敏 + PayeeDesensitizeUtil.desensitize(result); return result; } @@ -70,6 +75,8 @@ public class MchTransferService { Page page = alipayTransferOrderManager.page(pageParam, query); PageResult pageResult = toPageResult(page, AlipayTransferOrderConvert.CONVERT::toResult); transService.translate(pageResult); + // 收款人信息脱敏 + pageResult.getRecords().forEach(PayeeDesensitizeUtil::desensitize); return pageResult; } @@ -79,6 +86,8 @@ public class MchTransferService { .orElseThrow(() -> new DataNotExistException("pay.error.transfer.notFound")); AlipayTransferOrderResult result = AlipayTransferOrderConvert.CONVERT.toResult(entity); transService.translate(result); + // 收款人信息脱敏 + PayeeDesensitizeUtil.desensitize(result); return result; } @@ -87,6 +96,8 @@ public class MchTransferService { Page page = douyinTransferOrderManager.page(pageParam, query); PageResult pageResult = toPageResult(page, DouyinTransferOrderConvert.CONVERT::toResult); transService.translate(pageResult); + // 收款人信息脱敏 + pageResult.getRecords().forEach(PayeeDesensitizeUtil::desensitize); return pageResult; } @@ -96,6 +107,8 @@ public class MchTransferService { .orElseThrow(() -> new DataNotExistException("pay.error.transfer.notFound")); DouyinTransferOrderResult result = DouyinTransferOrderConvert.CONVERT.toResult(entity); transService.translate(result); + // 收款人信息脱敏 + PayeeDesensitizeUtil.desensitize(result); return result; } diff --git a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/encrypt/SecureAesGcmEncryptor.java b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/encrypt/SecureAesGcmEncryptor.java index 97327c578..f15eb2dee 100644 --- a/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/encrypt/SecureAesGcmEncryptor.java +++ b/daxpay-platform/daxpay-platform-common/common-config/src/main/java/cn/daxpay/open/platform/common/config/encrypt/SecureAesGcmEncryptor.java @@ -134,8 +134,13 @@ public class SecureAesGcmEncryptor { } /// 解密(根据版本前缀选择密钥) - /// @param ciphertext 密文,格式:v{version}:{encrypted} - /// @return 明文,如果找不到对应密钥则返回null + /// + /// 容错策略: 遇到非密文格式(如历史明文)或解密失败时, 原样返回输入值而非 null, + /// 保证新增加密字段的上线零风险(历史明文自动透传, 新数据正常加密), 支持渐进式迁移。 + /// 对已加密字段无影响(其 DB 值均为 v 前缀密文, 走正常解密路径)。 + /// + /// @param ciphertext 密文,格式:v{version}:{encrypted};或历史明文(原样返回) + /// @return 明文;若无法解密则原样返回输入值 public String decrypt(String ciphertext) { if (ciphertext == null) { return null; @@ -143,22 +148,22 @@ public class SecureAesGcmEncryptor { try { // 解析版本前缀 if (!ciphertext.startsWith(VERSION_PREFIX)) { - log.warn("密文格式错误,缺少版本前缀"); - return null; + // 非密文格式(历史明文或加密未启用时的明文), 原样透传 + return ciphertext; } int separatorIndex = ciphertext.indexOf(VERSION_SEPARATOR); if (separatorIndex == -1) { - log.warn("密文格式错误,缺少版本分隔符"); - return null; + log.warn("密文格式错误,缺少版本分隔符, 原样返回"); + return ciphertext; } int version; try { version = Integer.parseInt(ciphertext.substring(1, separatorIndex)); } catch (NumberFormatException e) { - log.warn("密文版本号格式错误"); - return null; + log.warn("密文版本号格式错误, 原样返回"); + return ciphertext; } String encryptedBase64 = ciphertext.substring(separatorIndex + 1); @@ -166,8 +171,8 @@ public class SecureAesGcmEncryptor { // 获取对应版本的密钥 SecretKey secretKey = secretKeyCache.get(version); if (secretKey == null) { - log.warn("找不到版本 v{} 对应的密钥", version); - return null; + log.warn("找不到版本 v{} 对应的密钥, 原样返回", version); + return ciphertext; } // 解密 @@ -185,8 +190,8 @@ public class SecureAesGcmEncryptor { byte[] decrypted = cipher.doFinal(encrypted); return new String(decrypted, StandardCharsets.UTF_8); } catch (Exception e) { - log.error("解密失败", e); - return null; + log.error("解密失败, 原样返回", e); + return ciphertext; } }