
1. 项目概述与核心价值最近几年在信息安全领域一个词的热度持续攀升那就是“国密”。对于很多从事Web开发、运维或者安全相关工作的朋友来说可能已经不止一次听到过SM2、SM3、SM4这些算法代号。简单来说国密算法是我国自主研发的一套商用密码算法标准旨在构建更自主可控的安全体系。其中SM2作为非对称加密算法对标的是国际通用的RSA和ECC在SSL/TLS协议中扮演着核心的密钥交换和身份认证角色。那么为什么我们要费劲去搭建一个支持国密SM2的Nginx服务器呢原因很直接合规与前瞻。在一些对信息安全有特定要求的行业和场景比如政务、金融、关键基础设施等领域使用国密算法进行通信加密已经不是“可选项”而是“必选项”。此外从技术自主和供应链安全的角度看掌握国密算法的应用部署能力也是我们技术人员一项重要的技能储备。然而当你真正动手尝试时很可能会发现这条路并不平坦。主流的Nginx默认编译并不支持国密算法你需要一个“国密化”的OpenSSL替代品——GmSSL并重新编译Nginx。这个过程充满了“坑”从GmSSL源码编译时的依赖缺失、配置参数错误到Nginx编译时链接库的路径问题、双证书的配置语法每一步都可能让你折腾半天。这篇教程的目的就是充当你的“领航员”和“避坑手册”。我将结合自己多次在CentOS和Ubuntu系统上实战的经验手把手带你从零开始完成GmSSL的编译安装、国密双证书的生成最终编译并配置出一个支持国密SM2的Nginx服务器。我会重点讲解那些官方文档可能一笔带过但实际操作中会让你卡壳的细节确保你能够一次成功或者至少知道问题出在哪里。2. 环境准备与GmSSL编译详解在开始编译之前一个干净、准备充分的Linux环境是成功的一半。我强烈建议使用一台全新的虚拟机或云服务器来操作避免系统中残留的其他OpenSSL版本带来冲突。我这里以CentOS 7.x或Ubuntu 20.04 LTS为例这两个是生产环境中最常见的系统。2.1 系统基础环境搭建首先我们需要安装编译所需的各类开发工具和库。这些是编译任何C/C项目的基础。对于CentOS/RHEL系列系统执行以下命令sudo yum groupinstall -y Development Tools sudo yum install -y wget git gcc-c pcre-devel zlib-devel perl-IPC-Cmd对于Ubuntu/Debian系列系统执行以下命令sudo apt-get update sudo apt-get install -y build-essential wget git libpcre3-dev zlib1g-dev perl这里有个关键点pcre-devel或libpcre3-dev和zlib-devel或zlib1g-dev是Nginx编译的核心依赖分别用于正则表达式支持和gzip压缩。如果缺失Nginx的configure脚本会直接报错退出。注意有些教程会建议安装openssl-devel但在我们的场景下要谨慎。因为我们要使用自己编译的GmSSL如果系统自带的OpenSSL开发包被安装可能会在后续链接时造成干扰。除非你明确知道自己在做什么否则先不要安装它。2.2 获取与编译GmSSL源码GmSSL是支持国密算法的OpenSSL分支我们的Nginx需要链接它。直接从Github克隆最新代码是推荐的做法。cd /usr/local/src sudo git clone https://github.com/guanzhi/GmSSL.git cd GmSSL进入目录后别急着运行./config。我们先要规划安装路径。将其安装到一个独立的目录比如/usr/local/gmssl可以很好地与系统自带的OpenSSL隔离。sudo ./config --prefix/usr/local/gmssl --openssldir/usr/local/gmssl/ssl这里解释一下参数--prefix/usr/local/gmssl指定安装根目录。编译后的可执行文件如gmssl命令、库文件.so和头文件.h都会安装在这个目录下。--openssldir/usr/local/gmssl/ssl指定OpenSSL配置和证书存储的目录。保持和prefix一致或在其子目录下便于管理。配置完成后就是经典的编译安装三步曲sudo make sudo make test # 强烈建议运行测试确保编译正确 sudo make installmake test这一步非常关键。它能验证你编译出的GmSSL库功能是否正常特别是国密算法相关的测试项。如果测试大面积失败通常意味着编译环境或源码有问题需要回头检查。安装完成后我们需要让系统知道这个新编译的GmSSL。通过设置环境变量来实现echo export PATH/usr/local/gmssl/bin:$PATH ~/.bashrc echo export LD_LIBRARY_PATH/usr/local/gmssl/lib:$LD_LIBRARY_PATH ~/.bashrc source ~/.bashrcPATH让我们能在终端直接输入gmssl命令。LD_LIBRARY_PATH告诉系统运行时去哪里查找GmSSL的动态链接库.so文件。这是后续Nginx编译能否成功链接GmSSL的关键。验证安装gmssl version你应该能看到输出类似GmSSL 3.1.1的版本信息。再运行gmssl ciphers -v | grep SM如果能看到ECC-SM2-SM4-CBC-SM3、ECC-SM2-SM4-GCM-SM3等套件说明国密算法支持已就绪。2.3 GmSSL编译常见“坑”与解决方案在实际操作中你大概率会遇到以下一两个问题make test失败提示/usr/bin/ld: cannot find -latomic问题原因这通常发生在一些较老的系统或特定架构上缺少链接原子操作库。解决方案安装libatomic库。CentOS:sudo yum install -y libatomicUbuntu:sudo apt-get install -y libatomic1安装后可能需要重新执行./config和make。编译过程中提示perl: warning: Falling back to a fallback locale (en_US.UTF-8).等本地化警告问题原因系统缺少必要的语言包虽然通常不影响编译结果但看着烦人。解决方案生成本地化配置。sudo locale-gen en_US.UTF-8 sudo update-locale LANGen_US.UTF-8 export LC_ALLen_US.UTF-8然后重新进入源码目录编译。gmssl命令找不到或者执行时报错找不到动态库问题原因环境变量未生效或者LD_LIBRARY_PATH设置不正确。解决方案确认~/.bashrc文件已修改并执行了source ~/.bashrc。使用绝对路径测试/usr/local/gmssl/bin/gmssl version。检查库路径是否存在ls /usr/local/gmssl/lib/查看是否有libcrypto.so、libssl.so等文件。对于某些严格的环境可能需要将库路径添加到系统配置echo /usr/local/gmssl/lib | sudo tee /etc/ld.so.conf.d/gmssl.conf然后执行sudo ldconfig。3. 生成国密SM2双证书链国密TLCP协议与传统的TLS一个显著区别就是“双证书”机制分别使用签名证书用于身份认证和加密证书用于密钥交换。因此我们需要生成一套完整的证书链包括自签名的根CA证书、服务器签名证书和服务器加密证书。3.1 初始化证书目录与配置文件为了管理清晰我们创建一个独立的工作目录。mkdir -p ~/sm2_certs cd ~/sm2_certs接下来我们需要GmSSL的配置文件模板。它通常安装在/usr/local/gmssl/ssl/openssl.cnf。我们可以直接使用它但为了自定义比如证书存放路径最好复制一份过来修改。cp /usr/local/gmssl/ssl/openssl.cnf .编辑这个openssl.cnf文件找到[ CA_default ]段落修改关键目录指向我们当前的工作目录这样后续生成证书时都会存放在这里方便管理。vim openssl.cnf找到并修改如下部分其他配置可保持不变[ CA_default ] dir /root/sm2_certs/demoCA # 所有CA相关文件的根目录 certs $dir/certs # 已签发证书存放位置 crl_dir $dir/crl # 证书吊销列表目录 database $dir/index.txt # 证书数据库索引文件 new_certs_dir $dir/newcerts # 新签发证书存放位置 certificate $dir/cacert.pem # CA自己的证书 serial $dir/serial # 当前序列号文件 private_key $dir/private/cakey.pem # CA的私钥保存退出后根据配置创建对应的目录和文件mkdir -p demoCA/{private,certs,crl,newcerts} touch demoCA/index.txt echo 1000 demoCA/serialindex.txt是一个空的证书数据库文件serial文件里的数字是下一个签发证书的序列号。3.2 生成根CA证书根证书是整个信任链的起点我们首先创建它。# 1. 生成SM2算法的CA私钥 gmssl ecparam -genkey -name sm2p256v1 -out demoCA/private/cakey.pem # 2. 生成CA证书请求CSR gmssl req -new -key demoCA/private/cakey.pem -out demoCA/careq.pem \ -subj /CCN/STBeijing/LBeijing/OMyOrg/OUGM Lab/CNMy Root CA # 3. 自签名生成根证书 gmssl x509 -req -days 3650 -sm3 -in demoCA/careq.pem \ -signkey demoCA/private/cakey.pem -out demoCA/cacert.pem-name sm2p256v1指定使用国密SM2椭圆曲线参数。-sm3指定使用国密SM3算法作为签名哈希算法。-subj设置证书主题信息按需修改。C国家ST省L市O组织OU部门CN通用名称这里作为CA名称。3.3 生成服务器双证书接下来为我们的Nginx服务器生成一对证书签名证书和加密证书。请注意它们对应两个不同的密钥对。首先生成签名证书# 1. 生成服务器签名私钥 gmssl ecparam -genkey -name sm2p256v1 -out server_sign.key # 2. 生成签名证书请求 gmssl req -new -key server_sign.key -out server_sign.csr \ -subj /CCN/STBeijing/LBeijing/OMyServer Inc./OUTech/CNsm2.demo.com # 3. 使用根CA签发服务器签名证书 gmssl x509 -req -days 365 -sm3 -in server_sign.csr \ -CA demoCA/cacert.pem -CAkey demoCA/private/cakey.pem -CAcreateserial \ -out server_sign.crt然后生成加密证书# 1. 生成服务器加密私钥 (这是一个新的密钥对) gmssl ecparam -genkey -name sm2p256v1 -out server_enc.key # 2. 生成加密证书请求 (注意这里的CN可以和签名证书一样代表同一个实体) gmssl req -new -key server_enc.key -out server_enc.csr \ -subj /CCN/STBeijing/LBeijing/OMyServer Inc./OUTech/CNsm2.demo.com # 3. 使用根CA签发服务器加密证书 gmssl x509 -req -days 365 -sm3 -in server_enc.csr \ -CA demoCA/cacert.pem -CAkey demoCA/private/cakey.pem -CAcreateserial \ -out server_enc.crt核心要点与避坑双密钥对server_sign.key和server_enc.key是两个完全不同的文件代表两个独立的SM2密钥对。这是国密双证书协议的要求切勿混用或试图用一个密钥对生成两个证书。证书格式生成的是PEM格式文本格式Nginx可以直接使用。如果需要其他格式如DER可以用gmssl x509 -in file.crt -outform DER -out file.der转换。证书链目前生成的server_sign.crt和server_enc.crt是终端实体证书。在某些客户端验证时可能需要提供完整的证书链即服务器证书根CA证书。你可以用cat server_sign.crt demoCA/cacert.pem server_sign_chain.crt来创建链式证书文件。4. 编译支持国密的Nginx有了GmSSL和国密证书下一步就是编译一个能识别它们的Nginx。我们需要下载Nginx源码并在编译时指定我们自定义的GmSSL路径。4.1 下载Nginx源码并配置访问Nginx官网下载稳定版源码这里以nginx-1.24.0为例。cd /usr/local/src sudo wget https://nginx.org/download/nginx-1.24.0.tar.gz sudo tar -zxvf nginx-1.24.0.tar.gz cd nginx-1.24.0关键的配置步骤来了。我们需要运行./configure脚本并告诉它GmSSL的位置。./configure \ --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_stub_status_module \ --with-openssl/usr/local/src/GmSSL \ --with-openssl-opt--prefix/usr/local/gmssl \ --with-cc-opt-I/usr/local/gmssl/include \ --with-ld-opt-L/usr/local/gmssl/lib -Wl,-rpath,/usr/local/gmssl/lib让我们拆解这些参数--prefix/usr/local/nginxNginx的安装目录。--with-http_ssl_module启用SSL/TLS模块这是必须的。--with-http_v2_module启用HTTP/2支持现代网站建议开启。--with-http_stub_status_module启用状态监控模块方便查看连接数等信息。--with-openssl/usr/local/src/GmSSL这是最核心的一步。它告诉Nginx不要使用系统自带的OpenSSL而是使用指定路径下的GmSSL源码进行编译。注意这里指向的是GmSSL的源码目录不是安装目录。--with-openssl-opt--prefix/usr/local/gmssl传递给GmSSL./config的额外参数确保编译Nginx时使用的GmSSL配置和我们之前安装的一致。--with-cc-opt-I/usr/local/gmssl/include指定GmSSL头文件的查找路径。--with-ld-opt-L/usr/local/gmssl/lib -Wl,-rpath,/usr/local/gmssl/lib指定GmSSL库文件的查找路径-L并设置运行时库路径-Wl,-rpath确保Nginx启动时能找到GmSSL的动态库。4.2 编译安装与验证配置完成后如果没有报错就可以编译安装了。sudo make sudo make install安装完成后验证Nginx是否链接到了正确的GmSSL库/usr/local/nginx/sbin/nginx -V在输出的巨量信息中找到configure arguments:那一部分确认里面有--with-openssl/usr/local/src/GmSSL。更关键的是查看输出的开头应该有一行包含built with OpenSSL后面跟着的版本信息应该显示GmSSL的版本号而不是系统OpenSSL的版本。这证明Nginx确实是基于GmSSL编译的。4.3 Nginx编译过程中的典型错误排查./configure错误SSL modules require the OpenSSL library.原因--with-openssl指向的路径不对或者该路径下没有GmSSL的源码缺少Configure、Makefile等文件。解决检查路径/usr/local/src/GmSSL是否存在并确认是通过git clone下载的完整源码。make错误fatal error: openssl/ssl.h: No such file or directory原因GmSSL的头文件路径未包含。--with-cc-opt参数可能未生效或路径错误。解决首先确认/usr/local/gmssl/include/openssl/ssl.h文件存在。然后检查configure命令是否完整正确地包含了--with-cc-opt-I/usr/local/gmssl/include。可以尝试手动指定CFLAGS-I/usr/local/gmssl/include ./configure ...。make错误undefined reference toSSL_CTX_set1_sigalgs_list‘ 或其他链接错误原因Nginx源码版本与GmSSL版本可能存在兼容性问题或者链接的库不对。解决这是一个比较棘手的问题。首先确保LD_LIBRARY_PATH环境变量已设置并生效echo $LD_LIBRARY_PATH。其次尝试清理后重新配置编译make clean ./configure ... 你的参数 make如果问题依旧可以尝试在./configure时增加--with-ld-opt-static尝试静态链接但这会增大二进制文件体积。最好的方法是检查GmSSL的libssl.a或libssl.so是否在/usr/local/gmssl/lib下并且版本匹配。5. 配置Nginx支持国密HTTPSNginx编译安装成功后位于/usr/local/nginx/。接下来就是配置SSL使其支持国密双证书。5.1 放置证书文件将之前生成的证书和私钥文件复制到Nginx的配置目录例如conf/ssl/下管理起来更清晰。sudo mkdir -p /usr/local/nginx/conf/ssl/ sudo cp ~/sm2_certs/server_sign.crt ~/sm2_certs/server_sign.key ~/sm2_certs/server_enc.crt ~/sm2_certs/server_enc.key /usr/local/nginx/conf/ssl/ sudo cp ~/sm2_certs/demoCA/cacert.pem /usr/local/nginx/conf/ssl/ # 根CA证书用于双向认证5.2 配置国密单向认证单向认证是最常见的场景服务器向客户端证明自己的身份。编辑Nginx的主配置文件/usr/local/nginx/conf/nginx.conf在http块内添加一个server块。http { ... # 其他原有配置 server { listen 443 ssl; server_name sm2.demo.com; # 替换为你的域名或IP # 国密SSL协议和密码套件配置 ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # GmSSL可能支持TLS1.3 ssl_ciphers ECC-SM2-SM4-CBC-SM3:ECC-SM2-SM4-GCM-SM3:ECDHE-RSA-AES128-GCM-SHA256; # 优先国密套件 ssl_prefer_server_ciphers on; # 关闭客户端证书验证单向认证 ssl_verify_client off; # 国密双证书配置这是关键 ssl_certificate ssl/server_sign.crt; ssl_certificate_key ssl/server_sign.key; ssl_certificate ssl/server_enc.crt; ssl_certificate_key ssl/server_enc.key; # 注意Nginx配置中后出现的指令会覆盖先出现的。 # 但双证书的配置是特殊的需要这样并列写两对。 location / { root html; index index.html index.htm; } # 可选启用状态页 location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } } }配置核心解读ssl_ciphers这里我们配置了国密套件ECC-SM2-SM4-CBC-SM3和ECC-SM2-SM4-GCM-SM3并将它们放在最前面表示优先使用。后面跟了一个国际算法套件作为备选以兼容不支持国密的客户端。双证书配置这是国密TLCP与普通TLS配置的核心区别。你需要用两对ssl_certificate和ssl_certificate_key指令分别指定签名证书和加密证书及其私钥。Nginx会正确处理它们。ssl_verify_client off表示不要求客户端提供证书即单向认证。5.3 配置国密双向认证在某些高安全要求场景需要双向认证mTLS即客户端也需要向服务器证明自己。这需要客户端也拥有由同一CA签发的证书。首先你需要为客户端生成证书过程类似服务器证书生成这里省略。然后在Nginx配置中启用客户端验证并指定信任的CA证书。server { listen 443 ssl; server_name sm2.demo.com; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ECC-SM2-SM4-CBC-SM3:ECC-SM2-SM4-GCM-SM3; ssl_prefer_server_ciphers on; # 开启客户端证书验证 ssl_verify_client on; # 指定信任的CA证书用于验证客户端证书 ssl_client_certificate ssl/cacert.pem; # 你的根CA证书 # ssl_verify_depth 2; # 可选设置证书链验证深度 # 国密双证书 ssl_certificate ssl/server_sign.crt; ssl_certificate_key ssl/server_sign.key; ssl_certificate ssl/server_enc.crt; ssl_certificate_key ssl/server_enc.key; ... # location等其他配置 }5.4 启动Nginx与测试配置完成后检查语法并启动。sudo /usr/local/nginx/sbin/nginx -t # 测试配置文件语法 sudo /usr/local/nginx/sbin/nginx # 启动Nginx sudo /usr/local/nginx/sbin/nginx -s reload # 重载配置如果已运行测试连接 由于主流浏览器Chrome, Firefox尚未默认支持国密TLCP协议我们需要使用支持国密的客户端进行测试。使用GmSSL的s_client工具最直接gmssl s_client -connect localhost:443 -servername sm2.demo.com -ciphersuites ECC-SM2-SM4-CBC-SM3如果连接成功你会看到详细的SSL握手信息包括证书、协议版本和使用的密码套件。在输出中寻找Cipher一行如果显示ECC-SM2-SM4-CBC-SM3或ECC-SM2-SM4-GCM-SM3就说明国密握手成功了使用国密浏览器可以下载并安装如“密信浏览器”等支持国密算法的浏览器进行访问测试。查看Nginx日志观察错误日志/usr/local/nginx/logs/error.log排查连接问题。6. 高级配置、优化与故障排除基础服务搭建起来后我们还需要关注性能、安全和运维方面的细节。6.1 性能与安全优化建议会话复用减少SSL握手开销。ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m;DH参数虽然SM2基于ECC但配置一个更强的DH参数文件有助于向后兼容和提升安全性对于支持DHE的套件。sudo openssl dhparam -out /usr/local/nginx/conf/ssl/dhparam.pem 2048在Nginx配置中添加ssl_dhparam ssl/dhparam.pem;安全头部增加HSTS等安全头部。add_header Strict-Transport-Security max-age63072000; includeSubdomains; preload;仅限国密套件如果确定客户端都支持国密可以禁用所有国际算法套件强制使用国密。ssl_ciphers ECC-SM2-SM4-CBC-SM3:ECC-SM2-SM4-GCM-SM3;6.2 常见故障与解决方案问题1Nginx启动失败错误日志显示SSL_CTX_set_cipher_list:no cipher match原因ssl_ciphers指令中指定的密码套件当前编译的NginxGmSSL不支持。解决首先用gmssl ciphers -v列出所有支持的套件确认ECC-SM2-SM4-CBC-SM3是否存在。如果不存在说明GmSSL编译时可能未正确启用国密算法需要重新检查GmSSL的编译和安装。如果存在检查Nginx配置中套件名称是否拼写错误。问题2客户端连接失败提示handshake failure或no shared cipher原因客户端不支持服务器端提供的国密套件。解决检查客户端是否真的支持国密如使用正确的测试工具。在Nginx的ssl_ciphers中增加一个通用的国际算法套件如ECDHE-RSA-AES128-GCM-SHA256作为后备测试客户端是否能以国际算法连接。这有助于判断问题是国密套件本身的问题还是完全无法连接。问题3双向认证时客户端证书验证失败原因客户端证书不是由ssl_client_certificate指定的CA签发的。客户端证书已过期或格式不正确。Nginx配置中ssl_verify_depth设置过小无法验证证书链。解决使用gmssl verify -CAfile /path/to/cacert.pem /path/to/client.crt命令验证客户端证书的有效性。确保Nginx配置中指向的ssl_client_certificate是签发客户端证书的根CA证书或中间CA证书链。尝试调大ssl_verify_depth例如设为5。问题4Nginx reload或重启后新配置不生效原因可能是证书文件路径错误或者证书/私钥文件权限问题导致Nginx工作进程无法读取。解决使用nginx -t严格测试配置文件语法。检查证书和私钥文件的路径是否绝对正确。检查私钥文件.key的权限确保Nginx进程用户通常是nobody或www-data有读取权限。建议设置为600sudo chmod 600 server_*.key。搭建支持国密的Nginx服务是一个系统工程涉及密码学、网络协议、系统编译和配置管理。整个过程最磨人的地方往往不是步骤本身而是环境差异导致的各类编译错误和配置问题。我的经验是保持环境干净严格按照步骤操作并理解每一步背后的目的这样在遇到问题时才能快速定位。当你在测试工具里看到Cipher: ECC-SM2-SM4-GCM-SM3那一行输出时所有的折腾都是值得的。这套环境不仅可以用于Web服务其原理同样适用于其他需要国密SSL支持的后端服务为你后续的国密应用开发打下了坚实的基础。如果在实践中遇到上面没覆盖到的问题多查看Nginx和GmSSL的错误日志那里面通常藏着最直接的线索。