From efcfc62a7b97e70ffcc5990a5e5eb79dd85efeae Mon Sep 17 00:00:00 2001 From: vincent Date: Fri, 6 Nov 2026 10:03:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=86=E8=AF=84=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/supervision/CommonController.java | 33 ++++++ .../service/ICryptoSensitiveHsmService.java | 15 +++ .../impl/CryptoSensitiveHsmServiceImpl.java | 105 ++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java index f94a48a7..4c177b36 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java @@ -154,4 +154,37 @@ public class CommonController extends BaseController { return R.ok("同步用户密评成功"); } + + + /** + * 数据完整性校验 + * @return + */ + @GetMapping("/verifyUserInfo") + public R> verifyUserInfo() { + + return R.ok("同步用户密评成功"); + } + + + /** + * 校验指定登录账号的数据完整性(HMAC-SM3 摘要与基线比对) + *

回查 sys_user 当前 password、phonenumber、certificateNumber 实际存储值, + * 重算 HMAC-SM3 摘要与 crypto_integrity_data 基线比对: + * verifyResult=1 通过、2 疑似篡改、3 跳过(用户不存在/已删除)、4 校验异常(非篡改)

+ * + * @param loginName 登录账号 + * @return 校验结果(userId、passed、verifyResult、fieldList、message) + */ + @GetMapping("/verifyUserDataConsistency") + public R> verifyUserDataConsistency( + @NotBlank(message = "登录账号不能为空") @RequestParam String loginName) { + log.info("指定账号数据完整性校验开始, loginName={}", loginName); + Map result = DataPermissionHelper.ignore( + () -> cryptoSensitiveHsmService.verifyUserDataConsistency(loginName)); + log.info("指定账号数据完整性校验结束, loginName={}, verifyResult={}, passed={}", + loginName, result.get("verifyResult"), result.get("passed")); + return R.ok("校验完成", result); + } + } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java index 6374e028..d7ee82d1 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java @@ -231,4 +231,19 @@ public interface ICryptoSensitiveHsmService { */ Map initSysUserIntegrityData(String loginName); + /** + * 校验指定登录账号的数据完整性(HMAC-SM3 摘要与基线比对) + *

+ * 以 crypto_integrity_data 中该用户的基准摘要为基线, 回查 sys_user 当前 + * password、phonenumber、certificateNumber 实际存储值重算 HMAC-SM3 摘要比对: + * 一致标记 verify_result=1(通过), 不一致标记 verify_result=2(疑似篡改), + * 用户已删除标记 verify_result=3(跳过), 校验过程异常标记 verify_result=4(校验异常, 非篡改), + * 同时刷新 last_verify_time。 + * 兼容存量旧口径基线(纯逗号拼接)匹配后平滑迁移为新口径。 + * + * @param loginName 登录账号(必填) + * @return 校验结果: userId、passed(是否通过)、verifyResult(1/2/3/4)、fieldList、message + */ + Map verifyUserDataConsistency(String loginName); + } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java index 23e8f1da..53696fe1 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java @@ -251,6 +251,14 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService return sysUserMapper.update(null, uw) > 0; } + /** + * 解密获取明文(SM4-CBC 对称解密) + * + * @param tableName 业务表名 + * @param bizId 业务记录主键 + * @param fieldCode 字段编码 + * @return 明文值,不存在则返回 null + */ @Override public String decryptField(String tableName, String bizId, String fieldCode) { CryptoSensitiveData record = queryActiveRecord(tableName, bizId, fieldCode); @@ -592,6 +600,103 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService return stats; } + @Override + public Map verifyUserDataConsistency(String loginName) { + if (StrUtil.isBlank(loginName)) { + throw new ServiceException("登录账号不能为空"); + } + SysUser user = DataPermissionHelper.ignore(() -> sysUserMapper.selectOne( + new LambdaQueryWrapper() + .eq(SysUser::getLoginName, loginName) + .eq(SysUser::getDelFlag, "0") + .last("limit 1"))); + if (user == null) { + throw new ServiceException("用户不存在: " + loginName); + } + Map result = new HashMap<>(); + result.put("loginName", loginName); + result.put("userId", user.getUserId()); + boolean allConsistent = true; + for (String fieldCode : new String[]{"password", "phonenumber", "certificateNumber"}) { + Map item = verifyOneFieldConsistency(user, fieldCode); + result.put(fieldCode, item); + if (!Boolean.TRUE.equals(item.get("consistent"))) { + allConsistent = false; + } + } + result.put("consistent", allConsistent); + log.info("指定账号敏感数据一致性校验完成, loginName={}, userId={}, 一致={}", + loginName, user.getUserId(), allConsistent); + return result; + } + + /** + * 单字段一致性校验(三口径): + * (1) 业务列值 == crypto_sensitive_data.cipher_text; + * (2) 备份字段 crypto_remark_* == crypto_sensitive_data.field_value(原逻辑值); + * (3) HSM 解密原逻辑值归一化到明文 == 备份字段解析明文(password 原逻辑值即 MD5, 与备份直接比对) + */ + private Map verifyOneFieldConsistency(SysUser user, String fieldCode) { + Map item = new HashMap<>(); + String bizId = String.valueOf(user.getUserId()); + String storedValue = switch (fieldCode) { + case "password" -> user.getPassword(); + case "phonenumber" -> user.getPhonenumber(); + case "certificateNumber" -> user.getCertificateNumber(); + default -> null; + }; + String remarkValue = getRemarkValue(user, fieldCode); + CryptoSensitiveData record = queryActiveRecord(TABLE_SYS_USER, bizId, fieldCode); + if (StrUtil.isBlank(storedValue) && record == null) { + item.put("status", "skip"); + item.put("consistent", true); + item.put("message", "字段为空且无密评记录, 跳过"); + return item; + } + if (record == null || StrUtil.isBlank(record.getCipherText())) { + item.put("status", "missingRecord"); + item.put("consistent", false); + item.put("message", "crypto_sensitive_data 无活跃密文记录"); + log.error("一致性校验: 密评记录缺失, userId={}, fieldCode={}", bizId, fieldCode); + return item; + } + boolean cipherMatch = StrUtil.isNotBlank(storedValue) && storedValue.equals(record.getCipherText()); + item.put("cipherMatch", cipherMatch); + boolean backupMatch = StrUtil.equals(remarkValue, record.getFieldValue()); + item.put("backupMatch", backupMatch); + boolean plainMatch; + try { + String originalForm = decryptField(TABLE_SYS_USER, bizId, fieldCode); + if (StrUtil.isBlank(originalForm)) { + plainMatch = false; + item.put("message", "HSM 解密失败"); + } else if ("password".equals(fieldCode)) { + plainMatch = originalForm.equals(remarkValue); + } else { + String hsmPlain = normalizeToPlain(fieldCode, originalForm); + String backupPlain = StrUtil.isBlank(remarkValue) ? null : normalizeToPlain(fieldCode, remarkValue); + plainMatch = StrUtil.isNotBlank(hsmPlain) && hsmPlain.equals(backupPlain); + } + } catch (Exception e) { + // 按规范: HSM 调用失败记录日志不中断流程, 标记校验异常(非篡改) + log.error("一致性校验 HSM 解密异常, userId={}, fieldCode={}", bizId, fieldCode, e); + plainMatch = false; + item.put("status", "hsmError"); + item.put("message", "HSM 解密异常: " + e.getMessage()); + } + item.put("plainMatch", plainMatch); + boolean consistent = cipherMatch && backupMatch && plainMatch; + item.put("consistent", consistent); + if (!consistent) { + item.put("status", StrUtil.isBlank((String) item.get("status")) ? "inconsistent" : item.get("status")); + log.error("敏感数据一致性校验未通过, userId={}, cipherMatch={}, backupMatch={}, plainMatch={}", + bizId, cipherMatch, backupMatch, plainMatch); + } else { + item.put("status", "ok"); + } + return item; + } + /** * 查询指定业务记录是否已存在完整性摘要 */