|
|
@ -3,6 +3,7 @@ package org.dromara.system.service.impl; |
|
|
import cn.hutool.core.date.DateUtil; |
|
|
import cn.hutool.core.date.DateUtil; |
|
|
import cn.hutool.core.util.HexUtil; |
|
|
import cn.hutool.core.util.HexUtil; |
|
|
import cn.hutool.core.util.StrUtil; |
|
|
import cn.hutool.core.util.StrUtil; |
|
|
|
|
|
import cn.hutool.crypto.SecureUtil; |
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
|
@ -24,12 +25,16 @@ import org.dromara.system.mapper.SysOperLogMapper; |
|
|
import org.dromara.system.mapper.SysUserMapper; |
|
|
import org.dromara.system.mapper.SysUserMapper; |
|
|
import org.dromara.system.service.ICryptoSensitiveHsmService; |
|
|
import org.dromara.system.service.ICryptoSensitiveHsmService; |
|
|
import org.dromara.system.service.IPasswordPartyService; |
|
|
import org.dromara.system.service.IPasswordPartyService; |
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.scheduling.annotation.Async; |
|
|
import org.springframework.scheduling.annotation.Async; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
|
|
|
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
import java.nio.charset.StandardCharsets; |
|
|
import java.util.Date; |
|
|
import java.util.Date; |
|
|
|
|
|
import java.util.HashMap; |
|
|
|
|
|
import java.util.List; |
|
|
|
|
|
import java.util.Map; |
|
|
|
|
|
|
|
|
@Slf4j |
|
|
@Slf4j |
|
|
@RequiredArgsConstructor |
|
|
@RequiredArgsConstructor |
|
|
@ -43,6 +48,9 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
private final SysUserMapper sysUserMapper; |
|
|
private final SysUserMapper sysUserMapper; |
|
|
private final SysOperLogMapper sysOperLogMapper; |
|
|
private final SysOperLogMapper sysOperLogMapper; |
|
|
|
|
|
|
|
|
|
|
|
@Value("${srnwg.secretKey}") |
|
|
|
|
|
private String secretKey; |
|
|
|
|
|
|
|
|
private static final String KEY_VERSION = "V1"; |
|
|
private static final String KEY_VERSION = "V1"; |
|
|
private static final String TABLE_SYS_USER = "sys_user"; |
|
|
private static final String TABLE_SYS_USER = "sys_user"; |
|
|
private static final String TABLE_SYS_OPER_LOG = "sys_oper_log"; |
|
|
private static final String TABLE_SYS_OPER_LOG = "sys_oper_log"; |
|
|
@ -50,6 +58,7 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
@Override |
|
|
@Override |
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
public String encryptAndSave(String tableName, String bizId, String fieldCode, String plainText) { |
|
|
public String encryptAndSave(String tableName, String bizId, String fieldCode, String plainText) { |
|
|
|
|
|
try{ |
|
|
if (StrUtil.isBlank(plainText)) { |
|
|
if (StrUtil.isBlank(plainText)) { |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
@ -81,6 +90,10 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
saveAuditRecord(tableName, bizId, fieldCode, record.getId(), 1, "HSM SM4-CBC 加密存储"); |
|
|
saveAuditRecord(tableName, bizId, fieldCode, record.getId(), 1, "HSM SM4-CBC 加密存储"); |
|
|
} |
|
|
} |
|
|
return encryptResult.getOutData(); |
|
|
return encryptResult.getOutData(); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("HSM加密保存失败, tableName={}, bizId={}, fieldCode={}", tableName, bizId, fieldCode, e); |
|
|
|
|
|
} |
|
|
|
|
|
return ""; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
@ -123,7 +136,17 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
@Override |
|
|
@Override |
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
public void saveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues) { |
|
|
public void saveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues) { |
|
|
try{ |
|
|
try { |
|
|
|
|
|
doSaveIntegrityHash(tableName, bizId, fieldList, plainValues); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("保存数据完整性摘要失败, tableName={}, bizId={}", tableName, bizId, e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 计算并 upsert 完整性摘要(异常向上抛出,供需要失败统计的调用方使用) |
|
|
|
|
|
*/ |
|
|
|
|
|
private void doSaveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues) { |
|
|
String hmacHash = calculateHash(fieldList, plainValues); |
|
|
String hmacHash = calculateHash(fieldList, plainValues); |
|
|
|
|
|
|
|
|
LambdaQueryWrapper<CryptoIntegrityData> lqw = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<CryptoIntegrityData> lqw = new LambdaQueryWrapper<>(); |
|
|
@ -155,13 +178,12 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
record.setIsDeleted(0); |
|
|
record.setIsDeleted(0); |
|
|
cryptoIntegrityDataMapper.insert(record); |
|
|
cryptoIntegrityDataMapper.insert(record); |
|
|
} |
|
|
} |
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("保存数据完整性摘要失败, tableName={}, bizId={}", tableName, bizId, e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public int verifyIntegrityHash(String tableName) { |
|
|
public int verifyIntegrityHash(String tableName) { |
|
|
|
|
|
try{ |
|
|
if (!TABLE_SYS_USER.equals(tableName) && !TABLE_SYS_OPER_LOG.equals(tableName)) { |
|
|
if (!TABLE_SYS_USER.equals(tableName) && !TABLE_SYS_OPER_LOG.equals(tableName)) { |
|
|
throw new ServiceException("暂不支持该业务表的完整性校验: " + tableName); |
|
|
throw new ServiceException("暂不支持该业务表的完整性校验: " + tableName); |
|
|
} |
|
|
} |
|
|
@ -203,8 +225,61 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
} |
|
|
} |
|
|
log.info("完整性校验完成, tableName={}, 处理={}, 通过={}, 异常={}", tableName, processed, passed, tampered); |
|
|
log.info("完整性校验完成, tableName={}, 处理={}, 通过={}, 异常={}", tableName, processed, passed, tampered); |
|
|
return processed; |
|
|
return processed; |
|
|
|
|
|
}catch (Exception e){ |
|
|
|
|
|
log.error("数据完整性校验失败, tableName={}", tableName, e); |
|
|
|
|
|
} |
|
|
|
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public Map<String, Integer> initSysUserSensitiveData(String loginName) { |
|
|
|
|
|
int total = 0, success = 0, skip = 0, fail = 0; |
|
|
|
|
|
long pageSize = 500L; |
|
|
|
|
|
Page<SysUser> page = new Page<>(1, pageSize); |
|
|
|
|
|
while (true) { |
|
|
|
|
|
Page<SysUser> result = DataPermissionHelper.ignore(() -> sysUserMapper.selectPage(page, |
|
|
|
|
|
new LambdaQueryWrapper<SysUser>() |
|
|
|
|
|
.eq(SysUser::getDelFlag, "0") |
|
|
|
|
|
.eq(StrUtil.isNotBlank(loginName), SysUser::getLoginName, loginName))); |
|
|
|
|
|
List<SysUser> records = result.getRecords(); |
|
|
|
|
|
for (SysUser user : records) { |
|
|
|
|
|
total++; |
|
|
|
|
|
String bizId = String.valueOf(user.getUserId()); |
|
|
|
|
|
// password:存储值(MD5)直接作为 HSM 加密输入,口径与 SynUserInfoJob 一致
|
|
|
|
|
|
int r = initOneField(bizId, "password", user.getPassword(), "^[0-9a-f]{32}$", false); |
|
|
|
|
|
if (r == 1) success++; else if (r == -1) fail++; else skip++; |
|
|
|
|
|
// phonenumber / certificateNumber:存量已是 AES 密文,先 AES 加密(已有)再 HSM 加密
|
|
|
|
|
|
r = initOneField(bizId, "phonenumber", user.getPhonenumber(), null, true); |
|
|
|
|
|
if (r == 1) success++; else if (r == -1) fail++; else skip++; |
|
|
|
|
|
r = initOneField(bizId, "certificateNumber", user.getCertificateNumber(), null, true); |
|
|
|
|
|
if (r == 1) success++; else if (r == -1) fail++; else skip++; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
doSaveIntegrityHash(TABLE_SYS_USER, bizId, |
|
|
|
|
|
"password,phonenumber,certificateNumber", |
|
|
|
|
|
StrUtil.nullToEmpty(user.getPassword()), |
|
|
|
|
|
StrUtil.nullToEmpty(user.getPhonenumber()), |
|
|
|
|
|
StrUtil.nullToEmpty(user.getCertificateNumber())); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("存量数据完整性摘要初始化失败, bizId={}", bizId, e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (!result.hasNext()) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
page.setCurrent(page.getCurrent() + 1); |
|
|
|
|
|
} |
|
|
|
|
|
log.info("sys_user 存量数据初始化完成: 扫描用户数={}, 成功字段数={}, 跳过字段数={}, 失败字段数={}", |
|
|
|
|
|
total, success, skip, fail); |
|
|
|
|
|
Map<String, Integer> stats = new HashMap<>(); |
|
|
|
|
|
stats.put("total", total); |
|
|
|
|
|
stats.put("success", success); |
|
|
|
|
|
stats.put("skip", skip); |
|
|
|
|
|
stats.put("fail", fail); |
|
|
|
|
|
return stats; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public int syncIntegrityHashModifiedUsers() { |
|
|
public int syncIntegrityHashModifiedUsers() { |
|
|
Date begin = DateUtil.beginOfDay(DateUtil.yesterday()); |
|
|
Date begin = DateUtil.beginOfDay(DateUtil.yesterday()); |
|
|
@ -271,6 +346,112 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService |
|
|
return processed; |
|
|
return processed; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public Map<String, Integer> initSysUserIntegrityData(String loginName) { |
|
|
|
|
|
int total = 0, success = 0, skip = 0, fail = 0; |
|
|
|
|
|
long pageSize = 500L; |
|
|
|
|
|
Page<SysUser> page = new Page<>(1, pageSize); |
|
|
|
|
|
while (true) { |
|
|
|
|
|
Page<SysUser> result = DataPermissionHelper.ignore(() -> sysUserMapper.selectPage(page, |
|
|
|
|
|
new LambdaQueryWrapper<SysUser>() |
|
|
|
|
|
.eq(SysUser::getDelFlag, "0") |
|
|
|
|
|
.eq(StrUtil.isNotBlank(loginName), SysUser::getLoginName, loginName))); |
|
|
|
|
|
List<SysUser> records = result.getRecords(); |
|
|
|
|
|
for (SysUser user : records) { |
|
|
|
|
|
total++; |
|
|
|
|
|
String bizId = String.valueOf(user.getUserId()); |
|
|
|
|
|
if (existsIntegrityRecord(TABLE_SYS_USER, bizId)) { |
|
|
|
|
|
skip++; |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
doSaveIntegrityHash(TABLE_SYS_USER, bizId, |
|
|
|
|
|
"password,phonenumber,certificateNumber", |
|
|
|
|
|
StrUtil.nullToEmpty(user.getPassword()), |
|
|
|
|
|
StrUtil.nullToEmpty(user.getPhonenumber()), |
|
|
|
|
|
StrUtil.nullToEmpty(user.getCertificateNumber())); |
|
|
|
|
|
success++; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("存量数据完整性摘要初始化失败, bizId={}", bizId, e); |
|
|
|
|
|
fail++; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (!result.hasNext()) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
page.setCurrent(page.getCurrent() + 1); |
|
|
|
|
|
} |
|
|
|
|
|
log.info("sys_user 存量完整性摘要初始化完成: 扫描用户数={}, 成功={}, 跳过={}, 失败={}", |
|
|
|
|
|
total, success, skip, fail); |
|
|
|
|
|
Map<String, Integer> stats = new HashMap<>(); |
|
|
|
|
|
stats.put("total", total); |
|
|
|
|
|
stats.put("success", success); |
|
|
|
|
|
stats.put("skip", skip); |
|
|
|
|
|
stats.put("fail", fail); |
|
|
|
|
|
return stats; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 查询指定业务记录是否已存在完整性摘要 |
|
|
|
|
|
*/ |
|
|
|
|
|
private boolean existsIntegrityRecord(String tableName, String bizId) { |
|
|
|
|
|
LambdaQueryWrapper<CryptoIntegrityData> lqw = new LambdaQueryWrapper<>(); |
|
|
|
|
|
lqw.eq(CryptoIntegrityData::getTableName, tableName) |
|
|
|
|
|
.eq(CryptoIntegrityData::getBizId, bizId) |
|
|
|
|
|
.eq(CryptoIntegrityData::getIsDeleted, 0); |
|
|
|
|
|
return cryptoIntegrityDataMapper.selectCount(lqw) > 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 初始化单个敏感字段(幂等) |
|
|
|
|
|
* |
|
|
|
|
|
* @param plainPattern 明文形态兜底正则(aesCipherInput=false 时使用) |
|
|
|
|
|
* @param aesCipherInput true=存储值为 AES 密文,AES 密文直接作为 HSM 加密输入(先 AES 后 HSM) |
|
|
|
|
|
* @return 1=成功、0=跳过、-1=失败 |
|
|
|
|
|
*/ |
|
|
|
|
|
private int initOneField(String bizId, String fieldCode, String storedValue, |
|
|
|
|
|
String plainPattern, boolean aesCipherInput) { |
|
|
|
|
|
if (StrUtil.isBlank(storedValue)) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
if (queryActiveRecord(TABLE_SYS_USER, bizId, fieldCode) != null) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
String hsmInput; |
|
|
|
|
|
if (aesCipherInput) { |
|
|
|
|
|
// 双加密规则:明文 → AES 加密 → HSM 加密。
|
|
|
|
|
|
// 存量即为 AES 密文,AES 解密仅用于形态判定,解密成功则原值直接作为 HSM 输入
|
|
|
|
|
|
if (!isAesCipherText(storedValue)) { |
|
|
|
|
|
log.warn("存量字段非AES密文形态, 跳过初始化, bizId={}, fieldCode={}", bizId, fieldCode); |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
hsmInput = storedValue; |
|
|
|
|
|
} else { |
|
|
|
|
|
hsmInput = (plainPattern != null && storedValue.matches(plainPattern)) ? storedValue : null; |
|
|
|
|
|
if (hsmInput == null) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
encryptAndSave(TABLE_SYS_USER, bizId, fieldCode, hsmInput); |
|
|
|
|
|
return 1; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("存量数据初始化失败, bizId={}, fieldCode={}", bizId, fieldCode, e); |
|
|
|
|
|
return -1; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 判断存储值是否为 AES 密文形态(用 srnwg.secretKey 尝试解密,成功即可解密) |
|
|
|
|
|
*/ |
|
|
|
|
|
private boolean isAesCipherText(String value) { |
|
|
|
|
|
try { |
|
|
|
|
|
return StrUtil.isNotBlank(SecureUtil.aes(SecureUtil.decode(secretKey)).decryptStr(value)); |
|
|
|
|
|
} catch (Exception ignored) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private CryptoSensitiveData queryActiveRecord(String tableName, String bizId, String fieldCode) { |
|
|
private CryptoSensitiveData queryActiveRecord(String tableName, String bizId, String fieldCode) { |
|
|
LambdaQueryWrapper<CryptoSensitiveData> lqw = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<CryptoSensitiveData> lqw = new LambdaQueryWrapper<>(); |
|
|
lqw.eq(CryptoSensitiveData::getTableName, tableName) |
|
|
lqw.eq(CryptoSensitiveData::getTableName, tableName) |
|
|
|