
1. 项目概述为什么我们需要关注国密SSL与双证书握手如果你最近在对接一些金融、政务或者对数据安全有特殊要求的系统大概率会遇到一个词“国密”。不是国际密码而是国家商用密码标准。简单来说这是一套我们自己的密码算法和协议体系核心算法包括SM2非对称加密、SM3哈希、SM4对称加密。而“国密SSL”就是基于这套国密算法栈实现的SSL/TLS协议。我最初接触国密SSL是因为一个银行的项目。对方要求通信链路必须支持国密算法套件并且明确要求使用“双证书”模式进行身份认证。当时用主流的OpenSSL试了一下发现它原生并不支持国密算法套件更别提双证书这种特殊流程了。折腾了一圈最终找到了GmSSL这个开源项目它完整实现了国密算法和国密TLCP协议是进行国密相关开发和测试的利器。这个项目标题“国密SSL实战用GmSSL实现双证书TLS1.2握手附OpenSSL对比”就精准地指向了三个核心痛点第一如何在一个实际环境中搭建并运行国密SSL第二如何理解并实现“双证书”这个关键且特殊的认证机制第三作为对比用我们熟悉的OpenSSL做同样的握手过程有何不同这不仅能帮你完成合规需求更能让你从协议层面理解国密SSL的设计思路。所以无论你是运维工程师需要配置国密网关还是开发者在写需要国密通信的客户端/服务端或者是安全研究员想了解协议细节这篇从环境搭建到抓包分析、从GmSSL实操到OpenSSL对比的完整记录都能给你一份可复现的参考。2. 核心概念解析国密算法、双证书与TLCP协议在动手之前我们必须把几个关键概念掰扯清楚。这就像盖房子前得认识砖、水泥和钢筋不然代码和配置摆在你面前也是一头雾水。2.1 国密算法三剑客SM2, SM3, SM4国密算法不是一个算法而是一个体系其中最常用的三个是SM2: 基于椭圆曲线密码ECC的非对称加密算法用于数字签名和密钥交换。你可以把它理解为国密版的RSA或ECDSA。但它的曲线参数、签名格式和RSA/ECDSA不同所以和它们互不兼容。一个常见的误区是认为SM2只是ECC换了个曲线实际上其签名算法如SM2-with-SM3和加密算法都有自己特定的格式。SM3: 密码杂凑算法生成256位的哈希值。功能上对标SHA-256但算法结构不同抗碰撞性等密码学强度有自身的设计考量。SM4: 分组对称加密算法分组长度和密钥长度均为128位。工作模式支持ECB、CBC、CFB、OFB、CTR等对标AES-128。在TLS协议中这些算法被组合成特定的“密码套件”。一个国密SSL密码套件看起来可能是这样的ECC-SM2-WITH-SM4-SM3。这表示使用SM2进行密钥交换和身份认证使用SM4进行对称加密使用SM3进行消息认证。2.2 双证书机制签名证书与加密证书分离这是国密TLCP协议及一些金融规范中的一个核心特性也是和传统RSA/ECDSA单证书模式最大的不同。签名证书专门用于身份认证和生成数字签名。私钥用于签名公钥放在证书里交给对方验证。这个证书的密钥对通常由实体自己生成并保管签名私钥强调不可抵赖性。加密证书专门用于密钥交换过程中的数据加密。例如在握手时客户端会用服务端的加密证书公钥来加密预主密钥。这个证书的密钥对有时可以由CA或密钥管理系统生成加密私钥可能被更严格地托管。为什么要分开主要出于安全和管理上的考虑职责分离签名密钥用于长期身份认证使用频率相对低加密密钥用于每次会话的密钥交换使用频率高。分开后即使加密私钥因高频使用而泄露也不会影响签名身份的安全性。密钥生命周期管理两种证书可以设置不同的有效期和更新策略。合规与审计在一些高安全场景下加密私钥的存储和使用可能有更严格的管控要求。在握手协议中服务端会在Certificate消息中连续发送两张证书第一张是签名证书第二张是加密证书。客户端需要能够正确解析并使用它们。2.3 TLCP协议与TLS 1.2TLCP是“Transport Layer Cryptography Protocol”的缩写即《GB/T 38636-2020 信息安全技术 传输层密码协议》。你可以把它理解为国密算法版的TLS 1.2协议。它在协议框架上基本遵循TLS 1.2但做出了关键修改强制双证书如上述服务端必须提供签名和加密双证书。固定的密码套件协议定义了必须支持的国密算法套件如ECC-SM2-WITH-SM4-SM3和ECDHE-SM2-WITH-SM4-SM3。密钥交换流程调整对于静态SM2密钥交换非ECDHE密钥交换消息的格式和计算方式与RSA密钥交换不同。签名算法必须使用sm2sig_sm3即SM2-with-SM3。因此一个支持TLCP的对端如GmSSL和一个仅支持标准TLS 1.2的对端如普通OpenSSL是无法直接建立连接的因为它们协商的密码套件列表和证书格式根本不匹配。3. 环境搭建与工具准备GmSSL编译与OpenSSL对比环境理论清楚了我们开始动手。整个实战需要在Linux环境下进行我使用的是Ubuntu 20.04主要工具就是GmSSL和OpenSSL并用Wireshark抓包分析。3.1 编译与安装GmSSLGmSSL是北京大学开源的项目GitHub上可以找到。不建议使用系统包管理器安装可能存在的旧版本自己编译能确保获得最新功能和支持。# 1. 克隆代码库 git clone https://github.com/guanzhi/GmSSL.git cd GmSSL # 2. 创建构建目录并配置 mkdir build cd build # 关键配置项安装到/usr/local/gmssl 启用静态库禁用无关模块加快编译 ../configure --prefix/usr/local/gmssl --enable-static --disable-docs # 3. 编译并安装 make -j$(nproc) # 使用多核编译加快速度 sudo make install # 4. 将GmSSL添加到环境变量避免与系统OpenSSL冲突 echo export PATH/usr/local/gmssl/bin:$PATH ~/.bashrc echo export LD_LIBRARY_PATH/usr/local/gmssl/lib:$LD_LIBRARY_PATH ~/.bashrc source ~/.bashrc # 5. 验证安装 gmssl version执行gmssl version应该输出类似“GmSSL 3.1.0”的信息。这里有个关键点gmssl命令被设计成与openssl命令兼容的子集所以很多openssl的子命令如genpkey,req,s_server,s_client在gmssl中同样可用但它内部使用的是国密算法。注意编译时如果遇到缺失的依赖如perl根据报错提示安装即可。--enable-static参数在某些需要静态链接的场景下有用如果只是动态链接可以不加。3.2 准备对比环境系统OpenSSL大多数Linux系统自带OpenSSL。我们用它来作为对比实验的服务端/客户端。openssl version确保版本在1.1.1以上即可。我们的目的是用OpenSSL搭建一个标准的RSA证书的TLS 1.2服务用来和GmSSL的国密双证书服务对比握手过程。3.3 证书生成生成SM2双证书与RSA单证书这是核心步骤。我们需要生成两套证书国密SM2双证书用于GmSSL服务端。传统RSA证书用于OpenSSL服务端作为对比。3.3.1 生成国密SM2双证书链国密证书通常使用SM2密钥和SM3哈希。我们需要生成一个自签的根CA然后用它来签发服务端的签名证书和加密证书。# 创建工作目录并进入 mkdir -p gmssl_certs cd gmssl_certs # 1. 生成SM2根CA私钥和自签证书 gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -pkeyopt ec_param_enc:named_curve -out ca.key gmssl req -new -key ca.key -out ca.csr -subj /CCN/STBeijing/LBeijing/OTest GmSSL CA/CNTest Root CA gmssl x509 -req -in ca.csr -signkey ca.key -sm3 -out ca.crt -days 3650 # 2. 生成服务端签名证书密钥对和CSR gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -pkeyopt ec_param_enc:named_curve -out server_sign.key gmssl req -new -key server_sign.key -out server_sign.csr -subj /CCN/STBeijing/LBeijing/OTest Server/CNserver.gmssl.test # 3. 生成服务端加密证书密钥对和CSR gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -pkeyopt ec_param_enc:named_curve -out server_enc.key gmssl req -new -key server_enc.key -out server_enc.csr -subj /CCN/STBeijing/LBeijing/OTest Server/CNserver.gmssl.test # 4. 用根CA分别签发两张服务端证书 gmssl x509 -req -in server_sign.csr -CA ca.crt -CAkey ca.key -CAcreateserial -sm3 -out server_sign.crt -days 365 -extfile (printf keyUsagedigitalSignature,nonRepudiation\nbasicConstraintsCA:FALSE) gmssl x509 -req -in server_enc.csr -CA ca.crt -CAkey ca.key -CAcreateserial -sm3 -out server_enc.crt -days 365 -extfile (printf keyUsagekeyEncipherment,dataEncipherment,keyAgreement\nbasicConstraintsCA:FALSE) # 5. 将两张证书合并为一个文件供GmSSL服务器使用签名证书在前加密证书在后 cat server_sign.crt server_enc.crt server_double.crt关键解释与避坑-pkeyopt ec_paramgen_curve:sm2p256v1这是指定生成SM2曲线参数的关键。如果只写-algorithm EC默认可能是secp256k1等曲线导致不是SM2算法。-sm3在x509命令中指定使用SM3作为签名哈希算法。这是生成国密证书必须的选项缺省可能是SHA256。-extfile我们通过这个参数为证书设置了扩展密钥用法。这是双证书的灵魂所在。签名证书的keyUsage包含digitalSignature数字签名和nonRepudiation不可否认。加密证书的keyUsage包含keyEncipherment密钥加密、dataEncipherment数据加密和keyAgreement密钥协商。客户端在验证证书时会检查这些扩展项确保证书被用于正确的用途。如果混用握手会失败。最后合并证书server_double.crt时顺序必须是签名证书在前加密证书在后。GmSSL的s_server在读取时会按顺序解析。3.3.2 生成传统RSA证书用于对比这个过程我们更熟悉用OpenSSL完成。# 返回上级目录创建对比用的证书目录 cd .. mkdir -p openssl_certs cd openssl_certs # 生成RSA私钥和自签证书一次性命令 openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj /CCN/STBeijing/LBeijing/OTest OpenSSL Server/CNserver.openssl.test这样我们就有了一个标准的、单证书的RSA密钥对。4. 实战演练启动服务与抓包分析握手过程现在我们同时启动两个服务并用客户端连接同时用Wireshark抓包。4.1 启动GmSSL国密双证书服务在一个终端中运行cd /path/to/gmssl_certs gmssl s_server -accept 8443 -key server_sign.key -cert server_double.crt -enc_key server_enc.key -enc_cert server_enc.crt -cipher ECC-SM2-WITH-SM4-SM3 -tls1_2 -www参数详解-accept 8443: 监听8443端口。-key server_sign.key: 指定签名证书的私钥。这是必须的因为ServerKeyExchange等签名消息需要用这个私钥。-cert server_double.crt: 指定证书链文件这里是我们合并的双证书文件。-enc_key server_enc.key:显式指定加密证书的私钥。这是GmSSL支持双证书的关键参数标准OpenSSL没有这个参数。-enc_cert server_enc.crt:显式指定加密证书文件。虽然-cert里包含了但这里再指定一次有助于内部逻辑处理。-cipher ECC-SM2-WITH-SM4-SM3: 强制使用这个国密密码套件。-tls1_2: 强制使用TLS 1.2协议。TLCP基于TLS 1.2。-www: 发送一个简单的状态页面响应给HTTP请求方便测试。4.2 启动OpenSSL RSA单证书服务在另一个终端中运行cd /path/to/openssl_certs openssl s_server -accept 9443 -key server.key -cert server.crt -tls1_2 -www这个命令更简洁因为它使用的是传统的单证书模式。4.3 使用GmSSL作为客户端进行连接测试打开第三个终端我们先测试国密连接。# 1. 连接GmSSL国密服务端口8443 echo -e GET / HTTP/1.0\r\n\r\n | gmssl s_client -connect localhost:8443 -cipher ECC-SM2-WITH-SM4-SM3 -tls1_2 -CAfile gmssl_certs/ca.crt # 2. 连接OpenSSL RSA服务端口9443—— 注意这里会失败 echo -e GET / HTTP/1.0\r\n\r\n | gmssl s_client -connect localhost:9443 -tls1_2第一个命令应该成功输出中会显示“SSL handshake has read ... bytes and written ... bytes”、“Verification: OK”以及最终的HTTP响应。这证明国密双证书握手成功。第二个命令几乎肯定会失败。错误信息可能是“no shared cipher”或者“sslv3 alert handshake failure”。为什么呢因为GmSSL客户端默认或我们指定的密码套件列表是国密套件而OpenSSL服务端只支持RSA/AES等国际标准套件双方找不到共同支持的套件握手失败。这直观地展示了国密SSL与国际标准TLS的不兼容性。4.4 使用OpenSSL作为客户端进行连接测试# 1. 连接OpenSSL RSA服务端口9443—— 应该成功 echo -e GET / HTTP/1.0\r\n\r\n | openssl s_client -connect localhost:9443 -tls1_2 # 2. 连接GmSSL国密服务端口8443—— 这里也会失败 echo -e GET / HTTP/1.0\r\n\r\n | openssl s_client -connect localhost:8443 -tls1_2第一个命令成功这是标准TLS握手。第二个命令失败原因同上OpenSSL客户端不支持国密套件无法与GmSSL服务端协商。4.5 Wireshark抓包深度对比分析这是理解协议差异最直接的方式。在开始所有测试前在另一个终端启动Wireshark或tcpdump捕获loopback接口lo上端口8443和9443的流量。sudo tcpdump -i lo -w tls_handshake.pcap port 8443 or port 9443分别执行上述4.3和4.4的客户端连接命令后停止抓包用Wireshark打开tls_handshake.pcap文件过滤tls。观察点1ClientHello的Cipher SuitesGmSSL客户端 - GmSSL服务端在ClientHello中你会看到Cipher Suites列表里包含像TLS_ECC_SM4_SM3 (0xe053)这样的值。这是国密套件的IANA临时编码。OpenSSL客户端 - OpenSSL服务端ClientHello中的Cipher Suites列表是类似TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384等标准套件。观察点2ServerHello选中的套件GmSSL交互ServerHello里选中的套件会是TLS_ECC_SM4_SM3。OpenSSL交互选中的会是某个标准套件如TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384。观察点3Certificate消息最关键的区别GmSSL服务端点击展开Certificate消息你会看到两个Certificate结构被依次包含在CertificateList中。第一个证书的扩展密钥用法是Digital Signature, Non-Repudiation第二个证书的扩展密钥用法是Key Encipherment, Data Encipherment, Key Agreement。这就是双证书在协议层面的直观体现。OpenSSL服务端Certificate消息的CertificateList里只有一个证书结构。观察点4Server Key Exchange消息在静态SM2密钥交换ECC-SM2-WITH-SM4-SM3中由于使用了服务端的SM2加密证书公钥所以不需要Server Key Exchange消息。预主密钥由客户端生成并用服务端加密公钥加密后在Client Key Exchange消息中发送。这与RSA密钥交换类似。在ECDHE-SM2套件中则需要Server Key Exchange消息来传递临时SM2公钥等参数。观察点5Finished消息的哈希算法整个握手消息的验证以及Finished消息的计算使用的都是SM3哈希算法而不是SHA256。通过抓包对比你可以清晰地看到从ClientHello的套件列表开始到Certificate消息的结构再到底层哈希算法国密SSL握手流程与国际标准TLS 1.2存在系统性差异。这不仅仅是“换了个算法”而是协议栈层面的适配。5. 常见问题、排查技巧与进阶思考在实际操作中你肯定会遇到各种报错。下面是我踩过的一些坑和解决方法。5.1 GmSSL常见错误与排查gmssl: error: ... no shared cipher原因客户端和服务端支持的密码套件列表没有交集。排查检查服务端启动命令是否用-cipher正确指定了国密套件如ECC-SM2-WITH-SM4-SM3。检查客户端连接时是否也指定了相同的套件或者客户端是否支持国密套件。用gmssl ciphers -v查看GmSSL支持的所有套件确认你指定的套件名称正确。心得国密套件名称是大小写敏感的最好直接从gmssl ciphers的输出里复制。gmssl: error: ... ssl handshake failure或sslv3 alert handshake failure这是一个更笼统的错误可能的原因很多。排查步骤证书问题优先这是最常见的原因。确认客户端是否使用了正确的CA证书-CAfile来验证服务端证书链。用gmssl verify -CAfile ca.crt server_double.crt验证服务端证书链是否完整且有效。双证书匹配确认服务端启动时-key指定的私钥是签名证书的私钥-enc_key指定的私钥是加密证书的私钥且与-cert、-enc_cert文件匹配。不匹配会导致签名或解密失败。密钥用法用gmssl x509 -in server_sign.crt -text -noout和gmssl x509 -in server_enc.crt -text -noout仔细检查两个证书的X509v3 Key Usage扩展项是否正确参见3.3.1节。如果签名证书没有digitalSignature或者加密证书没有keyEncipherment握手会失败。协议版本确保双方都使用了-tls1_2。GmSSL可能也支持TLS 1.3但国密TLCP目前基于TLS 1.2。抓包分析这是终极武器。在Wireshark中查看Alert消息的具体类型通常会给出更精确的错误原因如bad_certificate,unsupported_certificate,handshake_failure。gmssl s_server启动失败提示绑定端口失败检查端口是否被其他进程占用netstat -tlnp | grep :8443。GmSSL的s_server在某些版本下可能对参数顺序敏感确保-accept端口号在命令中位置正确。5.2 与现有系统集成的考量Nginx/Apache支持要让主流Web服务器支持国密SSL通常需要重新编译将OpenSSL替换为支持国密的密码库如GmSSL、TongSuo等并修改配置以加载双证书。Nginx的配置中可能需要使用ssl_certificate和ssl_certificate_key指令分别指定合并的证书文件和签名私钥同时需要通过其他模块或参数指定加密私钥这通常需要定制的Nginx模块支持。客户端兼容性你的客户端必须同样支持国密算法套件。对于浏览器需要安装支持国密的浏览器如密信浏览器或扩展。对于移动端APP或Java/Python等语言的客户端需要使用集成国密算法库的SSL实现如GmSSL的C库、BouncyCastle的国密Provider等。双向认证国密TLCP同样支持双向认证mTLS。此时客户端也需要提供双证书。流程上服务端在CertificateRequest消息中会请求客户端证书客户端则在Certificate消息中连续发送自己的签名证书和加密证书。5.3 性能与调试建议性能SM2算法基于椭圆曲线其性能与密钥长度相近的ECDSA/RSA相比各有优劣具体取决于实现优化。SM4的性能与AES相当。在实际部署前建议进行压力测试。调试除了WiresharkGmSSL和OpenSSL的s_client/s_server工具都提供了丰富的调试选项。-state打印SSL状态机转换。-debug输出更详细的调试信息。-msg以十六进制格式显示所有协议消息。组合使用这些参数可以让你对握手过程了如指掌。6. 总结与资源通过这一整套从编译、证书生成、服务启动、客户端测试到抓包分析的流程你应该对国密SSL特别是双证书握手机制有了非常直观和深入的理解。核心结论就是国密SSL不是简单地在OpenSSL里加几个算法而是一套从算法、证书格式到协议细节都有所不同的完整体系。关键资源GmSSL项目https://github.com/guanzhi/GmSSL国密标准文档GB/T 32918 (SM2), GB/T 32905 (SM3), GB/T 32907 (SM4), GB/T 38636 (TLCP)。这些是理解规范的基础。Wireshark协议分析必备工具最新版通常已经支持解析国密TLS的密码套件。最后个人体会是国密改造的第一步往往是“环境搭建”和“证书管理”这两件事理顺了后面的开发调试就会顺畅很多。尤其是在处理双证书时一定要理清签名和加密两对密钥证书的用途和匹配关系一个小的配置错误就可能导致握手失败而错误信息往往又不那么直观此时系统地按照证书链验证、密钥用法检查、抓包分析的步骤来排查是最有效率的方法。