Spring Boot 接入国密实战:传输加密(TLCP)+ 密码加密(SM4)

本文基于 Spring Boot 2.4 + JDK 8 某普查后端项目的真实落地代码整理,涵盖 链路层国密 HTTPS(TLCP)应用层登录密码 SM4 传输加密,可直接照做或裁剪到自己的项目。

代码仓库结构可参考:KonaTomcatSslConfigurationSm4CryptoServicePasswordCryptoServicescripts/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>

  1. http-enabled: falsetomcat.setPort(-1),去掉默认 HTTP 连接器;
  2. addAdditionalTomcatConnectors 增加 TLCP 连接器;
  3. sslImplementationName 指向 Kona 的 SSLImplementation
  4. 从 PKCS12 加载 SM2 签名/加密 双证到 SSLHostConfig

实现类:KonaTomcatSslConfiguration(适配自 Kona 官方 kona-demoTomcatServer)。

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: truehttp-enabled: false 专线、无 Nginx 或四层透传

五、密码传输加密:国密 SM4(应用层)

5.1 设计说明

  • 前端登录前调用 GET /api/sys/login/crypto 获取 keyiv(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
})

改密、重置密码等接口中的 oldpwdnewpwd 同样走 decryptTransport

5.5 与「存储加密」的区别

环节 算法 说明
传输 SM4-CBC(hex) 本文第五节
入库 BCrypt UserDetailsService 标准做法,不可逆
文件 SM4-GCM(SOILSM4 魔数) MinIO 附件等国密落盘,见 Sm4FileCryptoService

六、出站国密 HTTPS:gmRestTemplate

调用外部 TLCP 接口时注入:

java 复制代码
@Resource
@Qualifier("gmRestTemplate")
private RestTemplate gmRestTemplate;

KonaGmRestTemplateConfigkona.client.enabled=true 时用 Kona SSLContext(TLCP)构建 HttpComponentsClientHttpRequestFactory


七、实施步骤清单(可当 Checklist)

  1. pom 引入 kona-* + bcprov-jdk15on + hutool-all
  2. 启动类注册 KonaProviderBouncyCastleConfig
  3. KeyStoreTool + HmacPBESM3 生成 keystore.p12 / truststore.p12
  4. 配置 kona.ssl不要server.ssl
  5. 按需 http-enabled: false 仅开 8443
  6. 配置 login.password.encrypted=true 与 SM4 key/iv
  7. 实现 LoginCryptoController + PasswordCryptoService
  8. 前端对接 /sys/login/crypto 与 sm4 加密
  9. 国密浏览器验证 https://ip:8443/api/
  10. 生产更换密钥、证书密码,禁用演示证书

八、常见问题

现象 处理
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 接入国密,本项目的做法是:

  1. 传输层 :Kona 在 Tomcat 上增加 TLCP 8443,SM2 双证 PKCS12,可选关闭 HTTP;
  2. 应用层 :BouncyCastle/Hutool SM4-CBC 加密登录口令,接口下发 key/iv,登录前解密再 BCrypt;
  3. 扩展 :文件 SM4-GCM 、出站 gmRestTemplate、MFA 等与国密正交,可叠加。

按「传输 + 应用」两层拆分后,职责清晰、排查路径也简单:链路由 Kona/证书负责,字段由 SM4 负责。


相关推荐
念何架构之路2 小时前
Go pprof性能剖析
开发语言·后端·golang
人道领域2 小时前
【LeetCode刷题日记】617.合并二叉树(空间换安全,还是原地省内存)
java·数据结构·算法·leetcode
独自破碎E2 小时前
机器人Java后端算法笔试题解析
java·windows·算法
我是一颗柠檬2 小时前
【JDK8新特性】函数式接口Day2
java·开发语言·后端·intellij-idea
Trouvaille ~2 小时前
【Redis篇】Redis 安装与启动:快速搭建一个 Redis 环境
数据库·redis·后端·ubuntu·缓存·环境搭建·安装教程
Bat U2 小时前
JavaEE|JVM
java·jvm·java-ee
Mahir082 小时前
Spring Boot 自动装配深度解密:从原理到自定义 Starter 实战
java·spring boot·后端·自动装配·自定义starter·大厂面试题
淘源码d2 小时前
产科系统源码,数字产科源码,Java(后端) + Vue + ElementUI(前端) + MySQL(数据库),确保系统稳定性与扩展性。
java·源码·数字产科·产科系统·智能化孕产服务·高危五色预警·智慧产科
java1234_小锋2 小时前
Spring Boot 的嵌入式服务器(如 Tomcat)是如何启动的?如何替换为 Jetty 或 Undertow?
服务器·spring boot·tomcat