news 2026/9/19 16:33:40

GmSSL 与 Nginx 国密双证书配置实战:TLCP 改造避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GmSSL 与 Nginx 国密双证书配置实战:TLCP 改造避坑指南

1. 国密双证书到底解决了什么问题

第一次接触国密改造的项目,很多人会被“双证书”这个词绕晕。单证书我们熟,一张证书一把私钥,浏览器和服务器握个手就完事。但到了国密体系里,尤其是 TLCP 协议下,服务器需要同时持有一张签名证书和一张加密证书,这就是所谓的“双证书”。签名证书负责身份认证和握手过程中的签名操作,加密证书负责密钥交换和数据加密,两张证书各司其职,缺一不可。

为什么非要拆成两张?这跟国密 SM2 算法的设计理念有关。SM2 是基于椭圆曲线的公钥算法,既能做数字签名又能做密钥交换,但国密规范出于密钥管理的安全考虑,要求签名密钥和加密密钥分离。签名私钥使用频率高、暴露面大,加密私钥则涉及数据机密性,两者分开存储和管理,任何一张证书出问题都不会导致另一张的密钥泄露。这个思路在金融、政务领域特别重要,因为一旦加密私钥泄露,历史通信数据可能被批量解密,后果比签名私钥泄露严重得多。

GmSSL 是国内做国密改造最常用的开源工具之一,它提供了完整的 SM2、SM3、SM4 算法实现,以及 TLCP 协议栈。用 GmSSL 生成双证书,再配到 Nginx 上跑 TLCP,是目前国密网站改造的一条主流路径。这套流程听起来不复杂,但实际操作中坑不少:证书扩展字段怎么填、Nginx 用哪个版本、双证书怎么在配置里对应、客户端怎么验证,每一步都有讲究。

这篇文章面向的是需要做国密改造的运维和开发人员,不管你之前有没有接触过 GmSSL,只要你会基本的 Linux 操作和 Nginx 配置,跟着走一遍就能把双证书跑起来。我会从 GmSSL 的编译安装讲起,到双证书生成、Nginx 编译配置、TLCP 验证,把每个环节的原理和实操细节都摊开说。中间会穿插我自己踩过的坑和排查思路,这些内容在官方文档里基本找不到,但实际项目中一定会遇到。

2. GmSSL 环境搭建与工具链准备

2.1 为什么选 GmSSL 而不是 OpenSSL

OpenSSL 从 1.1.1 版本开始支持 SM2、SM3、SM4 算法,但它的 TLCP 协议支持一直不完整。OpenSSL 主要实现了国密算法,但没有完整实现 TLCP 握手流程中的双证书协商机制。GmSSL 则不同,它是北京大学密码学研究小组主导开发的项目,从底层就把国密标准作为核心目标,TLCP 协议栈、双证书管理、SM2 密钥交换都是原生支持。

另一个现实原因是 Nginx 的国密支持。Nginx 官方版本不支持 TLCP,需要打国密补丁或者使用支持国密的 Nginx 分支。目前社区里比较成熟的做法是使用 GmSSL 作为 SSL 库来编译 Nginx,这样 Nginx 就能直接处理 TLCP 握手。如果你用 OpenSSL 编译 Nginx,即使算法层面支持 SM2,协议层面也跑不通 TLCP。

提示:GmSSL 有 2.x 和 3.x 两个大版本,API 差异较大。Nginx 国密补丁目前主要适配 GmSSL 2.x 系列,选版本时要注意匹配。我实测下来 GmSSL 2.5.4 配合 Nginx 1.18.0 比较稳。

2.2 编译安装 GmSSL 的完整步骤

先从源码编译 GmSSL。不建议直接用系统包管理器安装,因为发行版仓库里的 GmSSL 版本往往偏旧,而且编译选项可能没开全。源码编译能确保我们拿到最新的稳定版和完整的 TLCP 支持。

# 下载 GmSSL 源码 wget https://github.com/guanzhi/GmSSL/archive/refs/tags/v2.5.4.tar.gz tar -zxvf v2.5.4.tar.gz cd GmSSL-2.5.4 # 配置编译选项 ./config --prefix=/usr/local/gmssl --openssldir=/usr/local/gmssl/ssl # 编译并安装 make -j$(nproc) sudo make install

编译完成后,把 GmSSL 的库路径加入系统配置,否则后续 Nginx 编译时找不到它:

echo "/usr/local/gmssl/lib" | sudo tee /etc/ld.so.conf.d/gmssl.conf sudo ldconfig

验证安装是否成功:

/usr/local/gmssl/bin/gmssl version

正常输出应该显示 GmSSL 2.5.4 以及编译时间。如果报错找不到库,检查 ld.so.conf 配置和 ldconfig 是否执行成功。

2.3 环境变量与路径配置的注意事项

GmSSL 安装后,命令行工具在/usr/local/gmssl/bin/gmssl,库文件在/usr/local/gmssl/lib。为了避免每次都要写全路径,可以把 bin 目录加入 PATH:

echo 'export PATH=/usr/local/gmssl/bin:$PATH' >> ~/.bashrc source ~/.bashrc

这里有个容易忽略的点:如果系统里已经装了 OpenSSL,opensslgmssl两个命令可能产生混淆。GmSSL 的命令行工具叫gmssl,不是openssl,两者参数风格类似但不完全兼容。比如 GmSSL 生成 SM2 密钥用的是gmssl sm2keygen,而 OpenSSL 用的是openssl ecparam,不要混用。

注意:编译 Nginx 时如果同时链接了 OpenSSL 和 GmSSL,可能出现符号冲突。建议在编译 Nginx 前确认--with-openssl指向的是 GmSSL 源码目录,而不是系统的 OpenSSL。

3. 国密双证书生成的核心细节

3.1 双证书的信任链结构解析

国密双证书不是两张孤立的证书,它们背后有一条完整的信任链。标准的国密证书体系通常包含三层:根 CA、中间 CA、终端实体证书。根 CA 自签名,中间 CA 由根 CA 签发,终端实体证书(也就是服务器的签名证书和加密证书)由中间 CA 签发。

为什么要有中间 CA?主要是为了安全隔离。根 CA 的私钥应该离线保存,日常签发证书用中间 CA 的私钥。这样即使中间 CA 被攻破,根 CA 还能签发新的中间 CA 来吊销旧的,整个信任体系不会崩塌。在实际项目中,如果你只是内部测试,可以简化成根 CA 直接签发终端证书,但生产环境强烈建议保留中间 CA 这一层。

签名证书和加密证书的密钥用途扩展字段不同。签名证书的keyUsage要包含digitalSignaturenonRepudiation,加密证书的keyUsage要包含keyEnciphermentdataEnciphermentextendedKeyUsage方面,签名证书填serverAuth,加密证书填clientAuth或者留空。这些扩展字段如果填错,TLCP 握手会直接失败,而且报错信息往往很模糊,排查起来很头疼。

3.2 用 GmSSL 生成 SM2 密钥对

先生成签名密钥和加密密钥。GmSSL 的sm2keygen命令可以生成 SM2 密钥对:

# 生成签名私钥 gmssl sm2keygen -pass 123456 -out sign_key.pem -pubout sign_pub.pem # 生成加密私钥 gmssl sm2keygen -pass 123456 -out enc_key.pem -pubout enc_pub.pem

-pass参数是私钥的保护密码,生产环境一定要设置强密码。-out是私钥输出文件,-pubout是公钥输出文件。生成的私钥是 PEM 格式,内部是 SM2 私钥的 DER 编码再做 Base64。

这里有个细节:GmSSL 生成的私钥默认带密码保护,后续签发证书时需要提供这个密码。如果你在脚本里自动化处理,密码管理要格外小心,不要把密码硬编码在脚本里。可以用环境变量或者密钥管理服务来传递。

3.3 构造证书请求与扩展字段配置

有了密钥对,接下来生成证书签名请求(CSR)。CSR 里包含公钥和主体信息,CA 用这些信息来签发证书。

# 生成签名证书的 CSR gmssl req -new -key sign_key.pem -passin pass:123456 \ -subj "/C=CN/ST=Beijing/L=Beijing/O=TestOrg/CN=sign.example.com" \ -out sign.csr # 生成加密证书的 CSR gmssl req -new -key enc_key.pem -passin pass:123456 \ -subj "/C=CN/ST=Beijing/L=Beijing/O=TestOrg/CN=enc.example.com" \ -out enc.csr

-subj里的 CN 字段,签名证书和加密证书可以不同,也可以相同。实际项目中,有些客户端会校验两张证书的 CN 是否一致,所以建议保持一致,避免兼容性问题。

扩展字段在 CSR 阶段可以不填,在 CA 签发时通过扩展配置文件指定。但更规范的做法是在 CSR 阶段就通过-extensions指定,这样 CA 签发时可以直接沿用。GmSSL 的扩展配置文件和 OpenSSL 格式类似,但支持的扩展项有差异,需要参考 GmSSL 的文档。

3.4 自建 CA 签发双证书的实操

测试环境我们可以自建 CA 来签发。生产环境应该用正式的 CA 机构,但自建 CA 能帮我们理解整个签发流程。

# 生成 CA 私钥 gmssl sm2keygen -pass 123456 -out ca_key.pem -pubout ca_pub.pem # 生成 CA 自签名证书 gmssl req -new -x509 -key ca_key.pem -passin pass:123456 \ -subj "/C=CN/ST=Beijing/L=Beijing/O=TestCA/CN=TestRootCA" \ -days 3650 -out ca_cert.pem

签发签名证书:

gmssl x509 -req -in sign.csr -CA ca_cert.pem -CAkey ca_key.pem \ -passin pass:123456 -CAcreateserial -days 365 \ -extfile sign_ext.cnf -out sign_cert.pem

sign_ext.cnf的内容:

[ v3_sign ] keyUsage = digitalSignature, nonRepudiation extendedKeyUsage = serverAuth subjectAltName = DNS:sign.example.com

签发加密证书:

gmssl x509 -req -in enc.csr -CA ca_cert.pem -CAkey ca_key.pem \ -passin pass:123456 -CAcreateserial -days 365 \ -extfile enc_ext.cnf -out enc_cert.pem

enc_ext.cnf的内容:

[ v3_enc ] keyUsage = keyEncipherment, dataEncipherment extendedKeyUsage = clientAuth subjectAltName = DNS:enc.example.com

签发完成后,把签名证书、加密证书、CA 证书和两个私钥整理好,Nginx 配置时需要用到。建议把证书链也拼好,有些客户端需要完整的证书链才能验证通过。

实操心得:GmSSL 的x509 -req命令在签发时,如果-extfile里的扩展项格式不对,可能不会报错但扩展字段也不会生效。签发后用gmssl x509 -in sign_cert.pem -text -noout检查一下,确认 keyUsage 和 extendedKeyUsage 都正确写入。

4. Nginx 国密改造与 TLCP 配置

4.1 Nginx 编译时链接 GmSSL 的关键参数

Nginx 官方版本不支持 TLCP,需要打国密补丁。目前社区里比较常用的方案是使用支持国密的 Nginx 源码包,或者给官方 Nginx 打补丁。我实测下来,Nginx 1.18.0 配合国密补丁比较稳定,1.20 以上版本补丁适配有些问题。

编译 Nginx 时,关键参数是--with-openssl指向 GmSSL 源码目录:

./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-openssl=/path/to/GmSSL-2.5.4 \ --with-openssl-opt="enable-ntls" \ --with-stream \ --with-stream_ssl_module make -j$(nproc) sudo make install

--with-openssl-opt="enable-ntls"这个参数很关键,它让 GmSSL 启用 NTLS(国密 TLS,也就是 TLCP)支持。如果不加这个参数,编译出来的 Nginx 虽然链接了 GmSSL,但 TLCP 握手会失败。

编译过程中如果报错找不到 GmSSL 的头文件,检查--with-openssl路径是否正确,以及 GmSSL 是否已经安装到系统路径。有时候需要手动指定--with-cc-opt--with-ld-opt来包含 GmSSL 的头文件和库路径。

4.2 双证书在 Nginx 配置中的对应关系

Nginx 的国密配置和普通 SSL 配置类似,但多了双证书的配置项。核心配置如下:

server { listen 443 ssl; server_name sign.example.com; # 签名证书和私钥 ssl_certificate /etc/nginx/certs/sign_cert.pem; ssl_certificate_key /etc/nginx/certs/sign_key.pem; # 加密证书和私钥 ssl_certificate /etc/nginx/certs/enc_cert.pem; ssl_certificate_key /etc/nginx/certs/enc_key.pem; # 国密协议配置 ssl_protocols TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-SM2-SM4-CBC-SM3:ECDHE-SM2-SM4-GCM-SM3:SM2-SM4-CBC-SM3; # 国密双证书模式 ssl_ntls on; location / { root /usr/share/nginx/html; index index.html; } }

ssl_ntls on是国密补丁引入的指令,开启后 Nginx 会使用 TLCP 协议处理握手。ssl_certificatessl_certificate_key出现两次,分别对应签名证书和加密证书。Nginx 会根据握手阶段自动选择使用哪张证书。

ssl_ciphers里配置的是国密套件。ECDHE-SM2-SM4-CBC-SM3表示密钥交换用 SM2,对称加密用 SM4-CBC,摘要用 SM3。ECDHE-SM2-SM4-GCM-SM3是 GCM 模式,性能更好但有些老客户端不支持。建议把 CBC 和 GCM 都配上,让客户端协商选择。

4.3 配置验证与常见报错处理

配置写完后,先用nginx -t检查语法:

/usr/local/nginx/sbin/nginx -t

如果报错unknown directive "ssl_ntls",说明 Nginx 编译时没有正确链接 GmSSL 或者没有启用 NTLS 支持。检查编译参数和 GmSSL 版本。

如果报错SSL_CTX_use_certificate相关,通常是证书文件路径不对或者证书格式有问题。用gmssl x509 -in cert.pem -text -noout确认证书能正常解析。

启动 Nginx 后,用 GmSSL 自带的客户端工具测试 TLCP 握手:

gmssl s_client -connect 127.0.0.1:443 -ntls -CAfile ca_cert.pem

如果握手成功,会显示协商出的国密套件和证书信息。如果失败,根据报错信息排查。常见的失败原因包括:证书扩展字段不对、CA 证书链不完整、客户端不支持国密套件。

注意:浏览器对 TLCP 的支持有限,Chrome 和 Firefox 默认不支持国密套件。测试时建议用 GmSSL 的 s_client 或者支持国密的专用浏览器。如果要在普通浏览器里测试,需要安装国密根证书并启用国密支持。

5. 常见问题排查与实战避坑指南

5.1 证书扩展字段错误的排查方法

TLCP 握手失败最常见的原因就是证书扩展字段不对。签名证书的 keyUsage 必须包含 digitalSignature,加密证书必须包含 keyEncipherment。如果这两个字段缺失或者填错,握手会在证书验证阶段失败。

排查方法是用gmssl x509 -in cert.pem -text -noout查看证书详情,重点看 X509v3 extensions 部分。如果 keyUsage 显示的是SSL Client或者SSL Server而不是具体的用途,说明扩展字段没写对。

另一个容易忽略的点是 basicConstraints。终端实体证书的 basicConstraints 应该是CA:FALSE,如果误设成CA:TRUE,有些客户端会拒绝。CA 证书的 basicConstraints 必须是CA:TRUE,否则签发链验证不过。

5.2 Nginx 启动失败的典型原因

Nginx 启动失败但nginx -t通过的情况,通常是运行时找不到 GmSSL 的库。用ldd /usr/local/nginx/sbin/nginx | grep gmssl检查动态库链接。如果显示not found,说明 ld.so.conf 配置有问题,或者 GmSSL 库路径没加入系统搜索路径。

还有一种情况是端口被占用。国密 Nginx 和普通 Nginx 如果同时运行,443 端口会冲突。可以用ss -tlnp | grep 443查看端口占用情况,或者给国密 Nginx 换一个端口。

如果 Nginx 启动后 TLCP 握手失败,但普通 HTTPS 正常,说明 GmSSL 链接没问题,问题出在国密配置上。检查ssl_ntls是否开启、双证书是否都配置了、ssl_ciphers里的国密套件是否正确。

5.3 客户端兼容性问题的处理思路

不同客户端对 TLCP 的支持程度差异很大。GmSSL 的 s_client 支持最完整,但浏览器支持参差不齐。如果项目要求浏览器访问,需要确认目标浏览器是否支持国密套件。

有些客户端只支持 SM2-SM4-CBC-SM3,不支持 GCM 模式。这种情况下,Nginx 的ssl_ciphers要把 CBC 套件放在前面,或者只配置 CBC 套件。另外,有些客户端对证书链的验证比较严格,需要把中间 CA 证书也配置到 Nginx 里。

# 配置完整证书链 ssl_certificate /etc/nginx/certs/sign_cert_with_chain.pem;

证书链的拼接顺序是:终端证书在前,中间 CA 在后,根 CA 可选。拼接时直接用文本编辑器把 PEM 文件内容按顺序拼在一起即可。

5.4 性能调优与安全加固建议

国密算法的性能比国际算法略低,SM2 的签名速度比 RSA 慢,SM4 的加密速度比 AES 慢。在高并发场景下,需要做一些调优。

首先是会话复用。TLCP 支持会话复用,可以减少握手开销:

ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets on;

其次是硬件加速。如果服务器有支持国密算法的硬件卡,可以启用硬件加速。GmSSL 支持通过引擎接口调用硬件卡,但配置比较复杂,需要根据具体硬件型号来调整。

安全加固方面,建议禁用不安全的套件,只保留必要的国密套件。同时配置 HSTS、OCSP Stapling 等安全特性。虽然国密体系本身安全性较高,但这些常规安全措施仍然建议保留。

实操心得:国密改造项目里,证书管理是最容易出问题的环节。建议把证书生成、签发、部署的流程脚本化,每次更新证书都走同样的流程,避免手工操作引入错误。另外,证书有效期要设置提醒,国密证书过期后排查起来比普通证书更麻烦,因为很多客户端报错信息不明确。

6. 从测试到生产的落地经验

测试环境跑通只是第一步,生产环境落地还有不少细节要处理。首先是证书的存储安全,私钥文件权限要严格控制,建议设置为 600,属主为 Nginx 运行用户。如果有多台服务器,私钥的分发要走安全通道,不要用明文传输。

其次是监控和告警。国密 Nginx 的 TLCP 握手失败率、证书有效期、会话复用率这些指标都要监控。GmSSL 和 Nginx 的日志可以配置详细级别,方便排查问题。建议在 Nginx 配置里开启ssl_verify_client的日志记录,虽然国密场景下通常不验证客户端证书,但日志能帮助定位握手问题。

最后是回滚方案。国密改造上线前,要准备好回滚到普通 HTTPS 的方案。万一国密配置出问题,能快速切回国际算法,保证业务不中断。回滚方案包括:保留普通 Nginx 的配置和证书、准备双栈配置(同时监听国密和普通端口)、做好 DNS 切换预案。

我在实际项目中遇到过一次比较典型的问题:国密 Nginx 上线后,部分老版本客户端握手失败,但新客户端正常。排查后发现是老客户端不支持 GCM 套件,只支持 CBC。把ssl_ciphers调整成 CBC 优先后问题解决。这个案例说明,国密改造不能只考虑服务端,客户端的兼容性测试同样重要,而且测试覆盖要尽可能广,把目标用户可能用到的客户端都测一遍。

另一个经验是,GmSSL 的版本升级要谨慎。不同版本之间的 API 和命令行参数可能有变化,升级前要在测试环境充分验证。特别是 Nginx 编译时链接的 GmSSL 版本,升级 GmSSL 后 Nginx 需要重新编译,否则可能出现运行时错误。建议把 GmSSL 版本和 Nginx 版本绑定管理,每次升级都走完整的测试流程。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 16:30:38

LLVM编译器基础设施详解:从IR构建到Pass优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 16:30:32

OpenStack多租户网络隔离实战:从VLAN到VXLAN的架构演进

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 16:30:06

机器人视觉导航中的图像内容匹配与SLAM全链路解析

简介:一份无人系统与智能机器人研究方向的高质量参考文献,核心是一篇发表于《光学 精密工程》的学术论文《结合图像内容匹配的机器人视觉导航定位与全局地图构建系统》。该研究针对室内自主定位中的“绑架”问题与相似物体干扰,提出了基于图像…

作者头像 李华
网站建设 2026/9/19 16:29:36

RK3568 MIPI DSI屏调试:uboot正常内核黑屏的软硬件全链路排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 16:29:35

Unity锁帧原理与热管理实战:从功耗控制到VR稳帧

1. 项目概述:为什么“锁帧”不是妥协,而是精密的热力学博弈“锁帧的智慧:拿帧率换发热余量”,这个标题里藏着一个被太多开发者轻描淡写、甚至误读的核心动作——Application.targetFrameRate。它不是一句简单的代码,而…

作者头像 李华
网站建设 2026/9/19 16:28:51

MCP与UE5.8实践:自然语言驱动游戏编辑器,构建AI辅助开发工作流

1. 从“对话”到“操作”:MCP在UE里的逻辑起点如果你最近在逛GitHub或技术社区,大概率会撞见MCP(Model Context Protocol)这个词。我第一次看到的时候也犯嘀咕,这不就是一个协议吗,怎么被吹得跟下一个“USB…

作者头像 李华