12 changed files with 289 additions and 42 deletions
@ -0,0 +1,57 @@ |
|||
package com.zdxt.enforcementcode.service; |
|||
|
|||
/** |
|||
* 密评敏感字段 HSM 加密服务 |
|||
* <p> |
|||
* 通过 HSM 密码服务平台对敏感字段进行加密存储,满足密评合规要求。 |
|||
* 手机号/身份证号使用 SM4-CBC 对称加密,密码使用 HMAC-SM3 单向哈希。 |
|||
*/ |
|||
public interface CryptoSensitiveHsmService { |
|||
|
|||
/** |
|||
* 加密并存储敏感字段(SM4-CBC 对称加密) |
|||
* <p> |
|||
* 调用 HSM /hsm/api/symmetryEncrypt 接口加密, |
|||
* 密文存入 crypto_sensitive_data,同时写入审计记录。 |
|||
* 若该 (tableName, bizId, fieldCode) 已有有效密文,先置为历史版本再插入新密文。 |
|||
* |
|||
* @param tableName 业务表名,如 sys_user |
|||
* @param bizId 业务记录主键 |
|||
* @param fieldCode 字段编码,如 phone / id_card |
|||
* @param plainText 明文值 |
|||
*/ |
|||
void encryptAndSave(String tableName, String bizId, String fieldCode, String plainText); |
|||
|
|||
/** |
|||
* 哈希并存储密码(HMAC-SM3 单向哈希) |
|||
* <p> |
|||
* 生成随机 salt,调用 HSM /hsm/api/HmacWithIndex 接口计算哈希, |
|||
* 哈希值存入 plain_hash,salt 存入 field_salt,cipher_text 为空。 |
|||
* |
|||
* @param tableName 业务表名 |
|||
* @param bizId 业务记录主键 |
|||
* @param plainPassword 明文密码 |
|||
* @return 生成的盐值(Hex),供登录校验时使用 |
|||
*/ |
|||
String hashAndSavePassword(String tableName, String bizId, String plainPassword); |
|||
|
|||
/** |
|||
* 验证密码是否匹配 |
|||
* |
|||
* @param tableName 业务表名 |
|||
* @param bizId 业务记录主键 |
|||
* @param plainPassword 明文密码 |
|||
* @return true=匹配 |
|||
*/ |
|||
boolean verifyPassword(String tableName, String bizId, String plainPassword); |
|||
|
|||
/** |
|||
* 解密获取明文(SM4-CBC 对称解密) |
|||
* |
|||
* @param tableName 业务表名 |
|||
* @param bizId 业务记录主键 |
|||
* @param fieldCode 字段编码 |
|||
* @return 明文值,不存在则返回 null |
|||
*/ |
|||
String decryptField(String tableName, String bizId, String fieldCode); |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
package com.zdxt.enforcementcode.service.impl; |
|||
|
|||
import cn.hutool.core.util.HexUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|||
import com.zdxt.enforcementcode.domain.bean.CryptoOperationAudit; |
|||
import com.zdxt.enforcementcode.domain.bean.CryptoSensitiveData; |
|||
import com.zdxt.enforcementcode.domain.vo.passwordparty.SymmetryEncryptVo; |
|||
import com.zdxt.enforcementcode.mapper.CryptoOperationAuditMapper; |
|||
import com.zdxt.enforcementcode.mapper.CryptoSensitiveDataMapper; |
|||
import com.zdxt.enforcementcode.service.CryptoSensitiveHsmService; |
|||
import com.zdxt.enforcementcode.service.thirdparty.IPasswordPartyService; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.dromara.common.satoken.utils.LoginHelper; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.SecureRandom; |
|||
|
|||
@Slf4j |
|||
@RequiredArgsConstructor |
|||
@Service |
|||
public class CryptoSensitiveHsmServiceImpl implements CryptoSensitiveHsmService { |
|||
|
|||
private final IPasswordPartyService passwordPartyService; |
|||
private final CryptoSensitiveDataMapper cryptoSensitiveDataMapper; |
|||
private final CryptoOperationAuditMapper cryptoOperationAuditMapper; |
|||
|
|||
private static final String KEY_VERSION = "V1"; |
|||
private static final String TABLE_SYS_USER = "sys_user"; |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void encryptAndSave(String tableName, String bizId, String fieldCode, String plainText) { |
|||
if (StrUtil.isBlank(plainText)) { |
|||
return; |
|||
} |
|||
String inputHex = HexUtil.encodeHexStr(plainText.getBytes(StandardCharsets.UTF_8)); |
|||
SymmetryEncryptVo encryptResult = passwordPartyService.symmetryEncrypt(inputHex); |
|||
|
|||
CryptoSensitiveData existing = queryActiveRecord(tableName, bizId, fieldCode); |
|||
if (existing != null) { |
|||
existing.setCipherText(encryptResult.getOutData()); |
|||
existing.setFieldIv(encryptResult.getIvData()); |
|||
existing.setFieldValue(plainText); |
|||
existing.setEncryptAlg("SM4-CBC"); |
|||
existing.setKeyVersion(KEY_VERSION); |
|||
cryptoSensitiveDataMapper.updateById(existing); |
|||
saveAuditRecord(tableName, bizId, fieldCode, existing.getId(), 3, "HSM SM4-CBC 加密更新"); |
|||
} else { |
|||
CryptoSensitiveData record = new CryptoSensitiveData(); |
|||
record.setTableName(tableName); |
|||
record.setBizId(bizId); |
|||
record.setFieldCode(fieldCode); |
|||
record.setFieldValue(plainText); |
|||
record.setCipherText(encryptResult.getOutData()); |
|||
record.setFieldIv(encryptResult.getIvData()); |
|||
record.setEncryptAlg("SM4-CBC"); |
|||
record.setKeyVersion(KEY_VERSION); |
|||
record.setIsActive(1); |
|||
record.setIsDeleted(0); |
|||
cryptoSensitiveDataMapper.insert(record); |
|||
saveAuditRecord(tableName, bizId, fieldCode, record.getId(), 1, "HSM SM4-CBC 加密存储"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public String hashAndSavePassword(String tableName, String bizId, String plainPassword) { |
|||
if (StrUtil.isBlank(plainPassword)) { |
|||
return null; |
|||
} |
|||
encryptAndSave(tableName, bizId, "password", plainPassword); |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public boolean verifyPassword(String tableName, String bizId, String plainPassword) { |
|||
CryptoSensitiveData record = queryActiveRecord(tableName, bizId, "password"); |
|||
if (record == null || StrUtil.isBlank(record.getCipherText()) || StrUtil.isBlank(record.getFieldIv())) { |
|||
return false; |
|||
} |
|||
try { |
|||
SymmetryEncryptVo decryptResult = passwordPartyService.symmetryDecrypt(record.getFieldIv(), record.getCipherText()); |
|||
byte[] plainBytes = hexToBytes(decryptResult.getOutData()); |
|||
String decryptedPassword = new String(plainBytes, StandardCharsets.UTF_8); |
|||
return decryptedPassword.equals(plainPassword); |
|||
} catch (Exception e) { |
|||
log.error("HSM解密密码验证失败, tableName={}, bizId={}", tableName, bizId, e); |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public String decryptField(String tableName, String bizId, String fieldCode) { |
|||
CryptoSensitiveData record = queryActiveRecord(tableName, bizId, fieldCode); |
|||
if (record == null || StrUtil.isBlank(record.getCipherText())) { |
|||
return null; |
|||
} |
|||
SymmetryEncryptVo decryptResult = passwordPartyService.symmetryDecrypt(record.getFieldIv(), record.getCipherText()); |
|||
byte[] plainBytes = hexToBytes(decryptResult.getOutData()); |
|||
return new String(plainBytes, StandardCharsets.UTF_8); |
|||
} |
|||
|
|||
private CryptoSensitiveData queryActiveRecord(String tableName, String bizId, String fieldCode) { |
|||
LambdaQueryWrapper<CryptoSensitiveData> lqw = new LambdaQueryWrapper<>(); |
|||
lqw.eq(CryptoSensitiveData::getTableName, tableName) |
|||
.eq(CryptoSensitiveData::getBizId, bizId) |
|||
.eq(CryptoSensitiveData::getFieldCode, fieldCode) |
|||
.eq(CryptoSensitiveData::getIsActive, 1) |
|||
.eq(CryptoSensitiveData::getIsDeleted, 0); |
|||
return cryptoSensitiveDataMapper.selectOne(lqw); |
|||
} |
|||
|
|||
private void deactivateExisting(String tableName, String bizId, String fieldCode) { |
|||
LambdaUpdateWrapper<CryptoSensitiveData> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(CryptoSensitiveData::getTableName, tableName) |
|||
.eq(CryptoSensitiveData::getBizId, bizId) |
|||
.eq(CryptoSensitiveData::getFieldCode, fieldCode) |
|||
.eq(CryptoSensitiveData::getIsActive, 1) |
|||
.set(CryptoSensitiveData::getIsActive, 0); |
|||
cryptoSensitiveDataMapper.update(null, uw); |
|||
} |
|||
|
|||
private void saveAuditRecord(String tableName, String bizId, String fieldCode, |
|||
Long sensitiveDataId, int operateType, String remark) { |
|||
CryptoOperationAudit audit = new CryptoOperationAudit(); |
|||
audit.setSensitiveDataId(sensitiveDataId); |
|||
audit.setTableName(tableName); |
|||
audit.setBizId(bizId); |
|||
audit.setFieldCode(fieldCode); |
|||
audit.setOperateType(operateType); |
|||
audit.setRemark(remark); |
|||
try { |
|||
audit.setOperatorId(LoginHelper.getUserId()); |
|||
} catch (Exception ignored) { |
|||
} |
|||
cryptoOperationAuditMapper.insert(audit); |
|||
} |
|||
|
|||
private static byte[] hexToBytes(String hex) { |
|||
int len = hex.length(); |
|||
byte[] data = new byte[len / 2]; |
|||
for (int i = 0; i < len; i += 2) { |
|||
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) |
|||
+ Character.digit(hex.charAt(i + 1), 16)); |
|||
} |
|||
return data; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue