本文基于 Spring Boot 2.4 + JDK 8 某普查后端项目的真实落地代码整理,涵盖 链路层国密 HTTPS(TLCP) 与 应用层登录密码 SM4 传输加密,可直接照做或裁剪到自己的项目。
代码仓库结构可参考:
KonaTomcatSslConfiguration、Sm4CryptoService、PasswordCryptoService、scripts/regenerate-kona-p12.ps1。
一、先分清两层「国密」
很多文章把「国密」混在一谈,落地时建议拆开:
| 层级 | 作用 | 典型算法/协议 | 本项目实现 |
|---|---|---|---|
| 传输层 | 浏览器/客户端 ↔ 服务端之间防窃听、防篡改 | TLCP(国密 SSL)、SM2 证书 | 腾讯 Kona SMSuite + 内嵌 Tomcat 8443 |
| 应用层 | 登录密码等字段在 HTTP 体内再加密 | SM4-CBC(与前端 sm-crypto 对齐) | BouncyCastle + Hutool SM4 |
| 存储层(补充) | 数据库密码哈希、文件落盘 | BCrypt、SM4-GCM | BCrypt + Sm4FileCryptoService |
传输层 解决「链路上被人抓包看到明文」;应用层解决「即便走了 HTTPS,也希望密码不以明文出现在请求 JSON 里」或「内网 HTTP + 应用层加密」的场景。
二者 建议同时使用:TLCP 保护全站流量,SM4 保护登录口令字段。
┌─────────────┐ TLCP/SM2证书 ┌──────────────────┐
│ 国密浏览器 │ ─────────────────► │ Spring Boot │
│ / 前端 │ HTTPS 8443 │ Tomcat TLCP │
└─────────────┘ │ │
│ │ LoginController │
│ POST /login │ decryptTransport│
│ password=SM4密文(hex) │ (SM4-CBC) │
└──────────────────────────►│ BCrypt 校验 │
└──────────────────┘
二、环境要求
| 项 | 建议 |
|---|---|
| JDK | 8u141+ (TLCP 最低);推荐 8u261+ 或 11/17 LTS |
| Spring Boot | 2.4.x(本文示例) |
| 国密浏览器 | 访问 TLCP 端口时需支持 TLCP 的浏览器(如奇安信、红莲花等) |
| 证书 | SM2 双证(签名 + 加密)+ 国密 CA 链 |
不必 更换为「腾讯 Kona JDK」;使用 Maven 引入 kona-* jar 即可在标准 OpenJDK 8 上运行。
三、Maven 依赖
3.1 传输层:腾讯 Kona
xml
<properties>
<kona.version>1.0.19</kona.version>
</properties>
<dependencies>
<dependency>
<groupId>com.tencent.kona</groupId>
<artifactId>kona-crypto</artifactId>
<version>${kona.version}</version>
</dependency>
<dependency>
<groupId>com.tencent.kona</groupId>
<artifactId>kona-pkix</artifactId>
<version>${kona.version}</version>
</dependency>
<dependency>
<groupId>com.tencent.kona</groupId>
<artifactId>kona-ssl</artifactId>
<version>${kona.version}</version>
</dependency>
<dependency>
<groupId>com.tencent.kona</groupId>
<artifactId>kona-provider</artifactId>
<version>${kona.version}</version>
</dependency>
</dependencies>
3.2 应用层:BouncyCastle(SM4)
xml
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.0</version>
</dependency>
四、传输加密:内嵌 Tomcat 开启 TLCP(8443)
4.1 注册 Kona Provider
在启动类 static 块 中注册(需早于任何 KeyStore/SSLContext 使用):
java
import com.soilsurvey.webapi.framework.config.kona.KonaProviderConfig;
static {
KonaProviderConfig.registerProvider();
}
KonaProviderConfig 内部:
java
if (Security.getProvider("Kona") == null) {
Security.addProvider(new KonaProvider());
}
4.2 配置项(不要用 server.ssl)
启用 kona.ssl 时 切勿 再配置 server.ssl.*,否则 Tomcat 会重复创建 HTTPS 连接器导致启动失败(Kona 官方 issue #347)。
yaml
server:
port: 8899
servlet:
context-path: /api/
kona:
ssl:
enabled: true
http-enabled: false # 仅 TLCP,关闭 HTTP 8899
https-port: 8443
protocol: TLCP
key-store-type: PKCS12
key-store: classpath:ssl/keystore.p12
key-store-password: 你的库密码
trust-store: classpath:ssl/truststore.p12
trust-store-password: 你的库密码
client-auth: false
client:
enabled: true # 出站调用国密 HTTPS 时使用
protocol: TLCP
key-store: classpath:ssl/keystore.p12
key-store-password: 你的库密码
trust-store: classpath:ssl/truststore.p12
trust-store-password: 你的库密码
启动成功后访问:https://主机:8443/api/(需国密浏览器)。
4.3 核心实现思路
通过 WebServerFactoryCustomizer<TomcatServletWebServerFactory>:
http-enabled: false时tomcat.setPort(-1),去掉默认 HTTP 连接器;addAdditionalTomcatConnectors增加 TLCP 连接器;sslImplementationName指向 Kona 的SSLImplementation;- 从 PKCS12 加载 SM2 签名/加密 双证到
SSLHostConfig。
实现类:KonaTomcatSslConfiguration(适配自 Kona 官方 kona-demo 的 TomcatServer)。
4.4 证书:从 Nginx PEM 生成 PKCS12(踩坑重点)
国密双证常见文件:
signcert.pem+signkey.pem(签名)enccert.pem+enckey.pem(加密)cacert.pem/rootcacert.pem(信任链)
使用 Kona 自带 KeyStoreTool 导入。生成时必须指定 国密 MAC,否则启动报:
text
Algorithm HmacPBESHA256 not available
原因 :KeyStoreTool 默认可能用 SHA256 MAC,而 Kona 的 PKCS12 只认 HmacPBESM3;同时 JDK 的 SunJSSE 无法解析 SM2 私钥条目,必须用 Kona 加载。
生成时 JVM 参数:
text
-Dcom.tencent.kona.keystore.pkcs12.macAlgorithm=HmacPBESM3
-Dcom.tencent.kona.keystore.pkcs12.keyPbeAlgorithm=PBEWithHmacSM3AndSM4
-Dcom.tencent.kona.keystore.pkcs12.certPbeAlgorithm=PBEWithHmacSM3AndSM4
PowerShell 一键脚本示例 (项目内 scripts/regenerate-kona-p12.ps1):
powershell
$KONA_CP = @(
"$env:USERPROFILE\.m2\repository\com\tencent\kona\kona-crypto\1.0.19\kona-crypto-1.0.19.jar",
"$env:USERPROFILE\.m2\repository\com\tencent\kona\kona-pkix\1.0.19\kona-pkix-1.0.19.jar",
"$env:USERPROFILE\.m2\repository\com\tencent\kona\kona-provider\1.0.19\kona-provider-1.0.19.jar"
) -join ';'
$JVM = '-Dcom.tencent.kona.keystore.pkcs12.macAlgorithm=HmacPBESM3',
'-Dcom.tencent.kona.keystore.pkcs12.keyPbeAlgorithm=PBEWithHmacSM3AndSM4',
'-Dcom.tencent.kona.keystore.pkcs12.certPbeAlgorithm=PBEWithHmacSM3AndSM4'
# 1) 签名证
java $JVM -cp $KONA_CP com.tencent.kona.pkix.tool.KeyStoreTool `
-type PKCS12 -alias sign -keyAlgo EC `
-key D:\nginx_gm\certs\signkey.pem -keyPasswd 私钥密码 `
-certs D:\nginx_gm\certs\p12\sign-chain.pem `
-store src\main\resources\ssl\keystore.p12 -storePasswd 库密码
# 2) 加密证(追加到同一 keystore)
java $JVM -cp $KONA_CP com.tencent.kona.pkix.tool.KeyStoreTool `
-type PKCS12 -alias enc -keyAlgo EC `
-key D:\nginx_gm\certs\enckey.pem -keyPasswd 私钥密码 `
-certs D:\nginx_gm\certs\p12\enc-chain.pem `
-store src\main\resources\ssl\keystore.p12 -storePasswd 库密码
# 3) 信任库
java $JVM -cp $KONA_CP com.tencent.kona.pkix.tool.KeyStoreTool `
-type PKCS12 -alias root,sub `
-certs D:\nginx_gm\certs\p12\trust-cas.pem `
-store src\main\resources\ssl\truststore.p12 -storePasswd 库密码
KonaSslStoreLoader 使用 Kona Provider 显式加载 PKCS12(不要用无参 KeyStore.getInstance("PKCS12"),否则在已注册 Kona 的 JVM 里仍会踩 MAC 算法坑)。
4.5 与 Nginx 的两种部署
| 模式 | 配置 | 适用 |
|---|---|---|
| Nginx 终结国密 | kona.ssl.enabled: false,Nginx proxy_pass http://127.0.0.1:8899 |
最常见,Java 只跑 HTTP |
| Java 直接 TLCP | kona.ssl.enabled: true,http-enabled: false |
专线、无 Nginx 或四层透传 |
五、密码传输加密:国密 SM4(应用层)
5.1 设计说明
- 前端登录前调用
GET /api/sys/login/crypto获取key、iv(32 位十六进制,各 16 字节); - 使用 sm-crypto (或等价库)对密码做 SM4-CBC + PKCS7 ,密文以 十六进制字符串 提交;
- 后端
PasswordCryptoService.decryptTransport()解密后,再走 Spring Security + BCrypt 校验。
注意 :key/iv 下发给前端属于「对称密钥协商简化版」,安全前提是 必须配合 HTTPS/TLCP 使用,并定期轮换密钥;生产环境建议改为 SM2 公钥加密传参 或 临时会话密钥。
5.2 配置
yaml
login:
password:
encrypted: true
algorithm: sm4 # sm4 | rsa(旧版兼容)
sm4-key: 72696e6e7947656e4b6c694e4a4b714e # 32 位 hex,生产务必更换
sm4-iv: 3f8a1c2b4d5e6f709182a3b4c5d6e7f0 # 32 位 hex
5.3 后端核心代码
SM4 加解密服务(与前端 sm-crypto 对齐,CBC/PKCS5Padding,密文 hex):
java
@Component
public class Sm4CryptoService {
@Value("${login.password.sm4-key:}")
private String sm4KeyHex;
@Value("${login.password.sm4-iv:}")
private String sm4IvHex;
public String decrypt(String cipherHex) {
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding,
HexUtil.decodeHex(sm4KeyHex),
HexUtil.decodeHex(sm4IvHex));
byte[] plain = sm4.decrypt(HexUtil.decodeHex(cipherHex));
return new String(plain, StandardCharsets.UTF_8);
}
}
统一入口(兼容旧 RSA):
java
@Component
public class PasswordCryptoService {
public String decryptTransport(String cipher) throws Exception {
if (!encrypted) return cipher;
if ("rsa".equalsIgnoreCase(algorithm)) {
return rsaCryptoService.dersa(cipher);
}
return sm4CryptoService.decrypt(cipher);
}
}
登录认证前解密:
java
protected UserToken authenticate(String username, String password) throws Exception {
password = passwordCryptoService.decryptTransport(password);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password);
return (UserToken) authenticationManager.authenticate(token).getPrincipal();
}
对外接口:
java
@GetMapping("/crypto")
public Result<LoginCryptoVo> crypto() {
return Result.OK(new LoginCryptoVo(true, "sm4",
sm4CryptoService.getKeyHex(), sm4CryptoService.getIvHex()));
}
5.4 前端示例(Vue + sm-crypto)
bash
npm install sm-crypto
javascript
import { sm4 } from 'sm-crypto'
// 1. 获取参数
const { data } = await axios.get('/api/sys/login/crypto')
const { key, iv, encrypted, algorithm } = data.data
if (!encrypted || algorithm !== 'sm4') {
// 明文模式
}
// 2. 加密密码(输出 hex,与后端 Hutool Hex 一致)
const cipherHex = sm4.encrypt(password, key, {
mode: 'cbc',
iv: iv,
padding: 'pkcs#7'
})
// 3. 登录
await axios.post('/api/sys/login', {
username,
password: cipherHex
})
改密、重置密码等接口中的 oldpwd、newpwd 同样走 decryptTransport。
5.5 与「存储加密」的区别
| 环节 | 算法 | 说明 |
|---|---|---|
| 传输 | SM4-CBC(hex) | 本文第五节 |
| 入库 | BCrypt | UserDetailsService 标准做法,不可逆 |
| 文件 | SM4-GCM(SOILSM4 魔数) |
MinIO 附件等国密落盘,见 Sm4FileCryptoService |
六、出站国密 HTTPS:gmRestTemplate
调用外部 TLCP 接口时注入:
java
@Resource
@Qualifier("gmRestTemplate")
private RestTemplate gmRestTemplate;
KonaGmRestTemplateConfig 在 kona.client.enabled=true 时用 Kona SSLContext(TLCP)构建 HttpComponentsClientHttpRequestFactory。
七、实施步骤清单(可当 Checklist)
- pom 引入
kona-*+bcprov-jdk15on+hutool-all - 启动类注册
KonaProvider、BouncyCastleConfig - 用 KeyStoreTool + HmacPBESM3 生成
keystore.p12/truststore.p12 - 配置
kona.ssl,不要 配server.ssl - 按需
http-enabled: false仅开 8443 - 配置
login.password.encrypted=true与 SM4 key/iv - 实现
LoginCryptoController+PasswordCryptoService - 前端对接
/sys/login/crypto与 sm4 加密 - 国密浏览器验证
https://ip:8443/api/ - 生产更换密钥、证书密码,禁用演示证书
八、常见问题
| 现象 | 处理 |
|---|---|
HmacPBESHA256 not available |
按第四节用 HmacPBESM3 重新生成 p12 |
parseAlgParameters failed |
JDK 无法读 SM2 keystore,必须用 Kona 加载 + 国密 MAC |
keystore password was incorrect |
库密码、PEM 私钥密码与 yml 不一致 |
| 8899 仍能访问 | 检查 http-enabled 是否已为 false 并 Rebuild |
| Swagger 打不开 | 关闭 HTTP 后请用 https://host:8443/api/swagger-ui.html |
| 登录仍明文 | 确认 login.password.encrypted=true 且前端调用了 /crypto |
调试 TLCP 握手:
text
-Dcom.tencent.kona.ssl.debug=all
九、总结
Spring Boot 接入国密,本项目的做法是:
- 传输层 :Kona 在 Tomcat 上增加 TLCP 8443,SM2 双证 PKCS12,可选关闭 HTTP;
- 应用层 :BouncyCastle/Hutool SM4-CBC 加密登录口令,接口下发 key/iv,登录前解密再 BCrypt;
- 扩展 :文件 SM4-GCM 、出站 gmRestTemplate、MFA 等与国密正交,可叠加。
按「传输 + 应用」两层拆分后,职责清晰、排查路径也简单:链路由 Kona/证书负责,字段由 SM4 负责。