在Java开发领域,安全认证框架Sa-Token因其轻量易用而广受欢迎。但随着安全合规要求日益严格,传统的MD5、SHA等加密算法已无法满足高安全级别的业务需求。最近我在金融项目中就遇到了这个问题——监管明确要求必须使用国密算法进行敏感数据加密。
国密算法(SM系列)是我国自主研发的密码算法体系,包括非对称加密SM2、哈希算法SM3和对称加密SM4。相比国际通用算法,国密算法在安全性、性能和合规性方面具有显著优势。本文将手把手教你如何在Sa-Token中集成国密算法,实现从密码加密到传输的全链路安全加固。
关键提示:生产环境务必使用真实的密钥管理系统,本文示例密钥仅用于演示
实现国密加密需要两个核心依赖:
在pom.xml中添加以下依赖(建议使用最新稳定版):
xml复制<!-- 国密算法底层支持 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<!-- 国密算法工具封装 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.37</version>
</dependency>
版本选择建议:
安装依赖后,可以通过简单测试验证环境是否就绪:
java复制public class EnvTest {
public static void main(String[] args) {
System.out.println("SM3哈希测试:" + SmUtil.sm3("test"));
System.out.println("SM4加密测试:" + SmUtil.sm4().encryptHex("test"));
}
}
若输出类似以下内容,说明环境配置成功:
code复制SM3哈希测试:66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0
SM4加密测试:3a8e...(省略)
密钥安全是加密系统的核心。我们采用分层管理策略:
SM2密钥对(非对称加密):
SM4密钥(对称加密):
示例密钥配置类:
java复制/**
* 国密算法密钥配置(生产环境必须替换!)
*/
public class KeyConfig {
// SM2公钥(BASE64编码)
public static final String SM2_PUB_KEY = "MFkwEw...";
// SM2私钥(BASE64编码)
public static final String SM2_PRI_KEY = "MIGTAgEAM...";
// SM4密钥(16字节Hex)
public static final String SM4_KEY = "08a9c1...";
// 密钥版本(用于轮换)
public static final String KEY_VERSION = "v1";
}
安全警示:示例密钥必须替换!真实项目应该:
- 使用KMS密钥管理系统
- 实现密钥自动轮换
- 禁止硬编码在代码中
完整工具类应包含以下功能:
java复制@Slf4j
public class GmCryptoUtils {
// SM2加密(前端->后端)
public static String sm2Encrypt(String data) {
SM2 sm2 = SmUtil.sm2(KeyConfig.SM2_PRI_KEY, KeyConfig.SM2_PUB_KEY);
return sm2.encryptBcd(data, KeyType.PublicKey);
}
// SM2解密(后端处理前端数据)
public static String sm2Decrypt(String encrypted) {
try {
SM2 sm2 = SmUtil.sm2(KeyConfig.SM2_PRI_KEY, KeyConfig.SM2_PUB_KEY);
return sm2.decryptStr(encrypted, KeyType.PrivateKey);
} catch (Exception e) {
log.error("SM2解密失败", e);
throw new CryptoException("解密失败");
}
}
// SM4加密(服务端存储)
public static String sm4Encrypt(String data) {
byte[] key = HexUtil.decodeHex(KeyConfig.SM4_KEY);
return SmUtil.sm4(key).encryptHex(data);
}
// SM4解密(服务端读取)
public static String sm4Decrypt(String encrypted) {
try {
byte[] key = HexUtil.decodeHex(KeyConfig.SM4_KEY);
return SmUtil.sm4(key).decryptStr(encrypted);
} catch (Exception e) {
log.error("SM4解密失败", e);
throw new CryptoException("解密失败");
}
}
// 带盐值的SM3哈希(密码存储)
public static String sm3WithSalt(String data, String salt) {
return SmUtil.sm3(data + salt);
}
}
关键设计要点:
修改Sa-Token的密码工具类,实现国密算法支持:
java复制public class GmSecurityUtils {
/**
* 密码加密(SM4+盐值)
*/
public static String encryptPassword(String password) {
String salt = IdUtil.fastSimpleUUID(); // 生成随机盐值
String encrypted = GmCryptoUtils.sm4Encrypt(password + salt);
return KeyConfig.KEY_VERSION + ":" + salt + ":" + encrypted;
}
/**
* 密码校验
*/
public static boolean checkPassword(String inputPwd, String storedPwd) {
String[] parts = storedPwd.split(":");
if (parts.length != 3) {
throw new CryptoException("密码格式错误");
}
String version = parts[0];
String salt = parts[1];
String encrypted = parts[2];
// 密钥版本校验(兼容轮换)
if (!KeyConfig.KEY_VERSION.equals(version)) {
throw new CryptoException("密钥版本不匹配");
}
String inputEncrypted = GmCryptoUtils.sm4Encrypt(inputPwd + salt);
return inputEncrypted.equals(encrypted);
}
}
存储格式说明:
code复制版本号:盐值:加密密码
示例:v1:5f3a8b:7d9e2a...
在Sa-Token的StpLogic中重写密码校验逻辑:
java复制public class GmStpLogic extends StpLogic {
@Override
protected boolean checkPassword(String loginPwd, String storedPwd) {
return GmSecurityUtils.checkPassword(loginPwd, storedPwd);
}
}
然后在配置中启用自定义逻辑:
java复制@Configuration
public class SaTokenConfig {
@Bean
public StpLogic getStpLogic() {
return new GmStpLogic();
}
}
前端需要使用支持国密的加密库,如sm-crypto:
javascript复制import { sm2 } from 'sm-crypto'
// 获取后端下发的SM2公钥
const publicKey = '04...'
// 密码加密
function encryptPassword(password) {
return sm2.doEncrypt(password, publicKey, 1) // 使用C1C3C2模式
}
登录接口需要支持加密密码处理:
java复制@PostMapping("/login")
public Result login(@RequestParam String username,
@RequestParam String encryptedPwd) {
// 解密前端传来的密码
String realPwd = GmCryptoUtils.sm2Decrypt(encryptedPwd);
// 执行Sa-Token登录
StpUtil.login(username);
StpUtil.getSession().set("user", getUser(username));
return Result.success();
}
推荐方案:
java复制// 阿里云KMS示例
public class KmsKeyLoader {
public static String getSm4Key() {
// 实际实现调用KMS API
return "动态获取的密钥";
}
}
SM2性能优化:
SM4批量处理:
java复制// 使用CBC模式更高效
SM4 sm4 = SmUtil.sm4(key).setMode(SM4.Mode.CBC);
缓存策略:
防御重放攻击:
完整性校验:
java复制// 加密数据增加MAC校验
String dataWithMac = data + "|" + SmUtil.sm3(data + key);
审计日志:
可能原因:
解决方案:
java复制try {
return sm2Decrypt(data);
} catch (Exception e) {
log.warn("SM2解密失败,尝试兼容处理");
// 尝试其他编码方式
return sm2.decryptStr(data, KeyType.PrivateKey, CharsetUtil.UTF_8);
}
优化方案:
java复制SM4 sm4 = SmUtil.sm4(key).setMode(SM4.Mode.CTR);
bash复制# JVM参数
-Dorg.bouncycastle.sm4.enable_aesni=true
平滑轮换策略:
实现示例:
java复制public static String decryptWithRotation(String encrypted) {
String[] parts = encrypted.split(":");
String version = parts[0];
if ("v1".equals(version)) {
return decryptV1(encrypted); // 旧密钥逻辑
} else if ("v2".equals(version)) {
return decryptV2(encrypted); // 新密钥逻辑
} else {
throw new CryptoException("不支持的密钥版本");
}
}
在实际项目中落地国密改造时,建议分三个阶段实施:
通过Sa-Token集成国密算法的实践,我们不仅满足了等保2.0等合规要求,更重要的是构建了自主可控的安全体系。这套方案已在多个金融项目中稳定运行,能够有效防御各种密码学攻击。