1. 项目概述与背景最近在折腾一个对数据安全要求比较高的内部项目客户明确要求通信链路必须支持国密算法。这让我不得不把几年前研究过的SM2证书体系又重新捡了起来。说实话虽然国密标准推行了这么多年但在实际的生产环境尤其是在主流的Linux发行版上要完整地走通一套基于SM2的自签名证书体系还是有不少坑要踩的。网上的资料要么是零零散散的片段要么就是基于一些特定的商业国密库对于想用标准OpenSSL自己动手搞定的朋友来说参考价值有限。所以我决定结合最近在Ubuntu 18.04上的一次完整实践写一份真正能“抄作业”的保姆级教程。我们的目标很明确在一台纯净的Ubuntu 18.04系统上使用官方OpenSSL 1.1.1版本从编译支持国密开始一步步创建出SM2算法的自签名根证书CA证书、服务器证书和客户端证书并完成基本的验证。整个过程我会把每一个命令、每一个参数、每一个可能报错的地方都解释清楚。无论你是为了满足合规要求还是单纯对国密技术感兴趣跟着做下来你都能拥有一个完全受自己控制的SM2证书环境为后续的HTTPS、双向认证等应用打下坚实基础。注意Ubuntu 18.04自带的OpenSSL 1.1.1默认并不支持国密算法。因此我们的第一步必须是手动编译一个支持国密的OpenSSL。这是整个流程的基石绕不过去。2. 环境准备与OpenSSL国密支持编译2.1 系统环境初始化首先确保我们有一台干净的Ubuntu 18.04 LTS系统。可以是物理机、虚拟机VMware/VirtualBox或者WSL2环境。我强烈建议在虚拟机中操作方便做快照折腾坏了也能瞬间恢复。登录系统后第一件事是更新软件源并安装必要的编译工具和依赖库。这些工具是编译OpenSSL的必需品。sudo apt update sudo apt upgrade -y sudo apt install build-essential checkinstall zlib1g-dev -ybuild-essential提供了GCC编译器、make等核心编译工具checkinstall能帮助我们更好地管理从源码安装的软件包zlib1g-dev是OpenSSL可能用到的压缩库开发文件。接下来为我们的编译工作创建一个独立、整洁的目录避免文件散落各处。mkdir -p ~/openssl_sm2_build cd ~/openssl_sm2_build2.2 获取并编译支持国密的OpenSSL 1.1.1Ubuntu 18.04仓库里的OpenSSL 1.1.1版本虽然不低但缺了国密这块拼图。我们需要获取源码并开启相应的编译选项。第一步下载源码包。我们选择OpenSSL 1.1.1系列的最终版本1.1.1w它修复了之前版本的许多安全漏洞相对稳定。当然你也可以尝试更新的1.1.1系列版本但编译参数基本通用。wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar -xzf openssl-1.1.1w.tar.gz cd openssl-1.1.1w第二步配置编译选项。这是最关键的一步。./config命令用于生成Makefile。我们需要指定安装路径--prefix并明确启用国密算法支持。./config --prefix/usr/local/openssl-1.1.1w \ --openssldir/usr/local/openssl-1.1.1w/ssl \ zlib \ no-weak-ssl-ciphers \ no-ssl3 \ no-shared \ enable-ec \ enable-ec_nistp_64_gcc_128 \ enable-sm2 \ enable-sm3 \ enable-sm4让我逐一解释这些参数--prefix和--openssldir指定了OpenSSL的安装目录。我们将其安装到/usr/local/下与系统自带的OpenSSL通常在/usr/bin/openssl隔离避免冲突。zlib启用zlib压缩支持。no-weak-ssl-ciphers和no-ssl3禁用不安全的加密套件和过时的SSL 3.0协议提升安全性。no-shared这个参数非常重要它告诉编译器只生成静态库.a文件而不生成动态库.so文件。在后续我们使用这个自编译的OpenSSL生成SM2证书时如果依赖动态库可能会因为链接路径问题导致找不到国密算法符号。使用静态库编译出的openssl可执行文件是自包含的可以独立运行彻底杜绝此类问题。enable-ec,enable-ec_nistp_64_gcc_128启用椭圆曲线基础支持和特定的优化这是SM2算法基于椭圆曲线的依赖。enable-sm2,enable-sm3,enable-sm4核心目标启用国密SM2非对称加密/签名、SM3杂凑算法和SM4对称加密算法支持。第三步编译与安装。使用make命令进行编译这个过程可能会花费几分钟取决于你的机器性能。编译完成后使用sudo make install安装到之前指定的目录。make -j$(nproc) # 使用多核并行编译以加快速度 sudo make install-j$(nproc)会自动检测你CPU的核心数并启动相应数量的编译任务能显著加快编译速度。第四步验证安装并设置环境变量。安装完成后我们需要验证自编译的OpenSSL是否正常工作且支持国密。/usr/local/openssl-1.1.1w/bin/openssl version你应该能看到输出OpenSSL 1.1.1w ...。接下来检查国密算法是否在列表中/usr/local/openssl-1.1.1w/bin/openssl ecparam -list_curves | grep -i sm2如果看到类似SM2 (1.2.156.10197.1.301)的输出恭喜你SM2支持已经成功编译进去了为了方便后续操作避免每次都要输入完整路径我们可以临时将自编译OpenSSL的bin目录加入当前会话的PATH环境变量。export PATH/usr/local/openssl-1.1.1w/bin:$PATH which openssl # 此时应指向 /usr/local/openssl-1.1.1w/bin/openssl openssl version # 再次确认版本实操心得编译时务必加上no-shared参数。我曾经图省事没加结果在生成SM2密钥时总是报错unknown curve name排查了半天才发现是动态库链接问题。静态编译一劳永逸。另外--prefix指定的安装目录要有写权限所以前面的sudo make install是必须的。3. SM2证书体系核心概念与规划在开始敲命令之前花几分钟理解我们要构建的证书体系是很有必要的。这能帮你明白每一步在做什么而不是机械地复制粘贴。3.1 什么是SM2证书简单说SM2证书就是使用国密SM2算法作为密钥对公钥和私钥的X.509证书。其结构和标准与常见的RSA证书、ECC证书完全相同都遵循X.509 v3标准包含版本、序列号、签名算法、颁发者、有效期、主体、公钥信息、扩展项等字段。唯一的区别在于签名算法标识从sha256WithRSAEncryption或ecdsa-with-SHA256变成了sm2sign-with-sm3。公钥算法公钥信息部分描述的是SM2椭圆曲线公钥参数。签名值使用SM2私钥和SM3杂凑算法计算得出。3.2 自签名根证书CA的作用在我们的实验中没有外部的证书颁发机构CA所以我们需要自己创建一个“根CA”。这个自签名的根证书是整个信任链的起点。它的核心作用是签发下级证书用根CA的私钥为服务器证书或客户端证书签名证明它们的合法性。充当信任锚任何信任这张根证书的系统如浏览器、客户端程序都会自动信任由它签发的所有证书。你可以把它想象成一家自己开的“公安局”你给自己颁发的“身份证”在自己的王国里是有效的。3.3 证书文件规划为了清晰管理我们预先规划好目录结构和文件命名~/sm2_certs_demo/ ├── root_ca/ # 根CA相关文件 │ ├── private/ # 存放根CA的私钥务必保密 │ │ └── root_ca.key │ ├── certs/ # 存放根CA的证书 │ │ └── root_ca.crt │ └── index.txt # 证书数据库文件OpenSSL CA模式需要 ├── server/ # 服务器证书相关文件 │ ├── private/ │ │ └── server.key │ └── certs/ │ └── server.crt ├── client/ # 客户端证书相关文件如需 │ ├── private/ │ │ └── client.key │ └── certs/ │ └── client.crt └── openssl.cnf # 自定义的OpenSSL配置文件现在创建这个目录结构mkdir -p ~/sm2_certs_demo/root_ca/{private,certs} mkdir -p ~/sm2_certs_demo/server/{private,certs} mkdir -p ~/sm2_certs_demo/client/{private,certs} cd ~/sm2_certs_demo touch root_ca/index.txt echo 1000 root_ca/serial # 初始化证书序列号3.4 准备自定义OpenSSL配置文件OpenSSL的ca和req命令的行为很大程度上由一个配置文件控制。系统默认的/etc/ssl/openssl.cnf可能不适合我们特别是涉及国密算法和自定义目录时。我们创建一个简化版的自定义配置。创建文件openssl.cnf内容如下[ ca ] default_ca CA_default [ CA_default ] dir ./root_ca certs $dir/certs new_certs_dir $dir/certs database $dir/index.txt serial $dir/serial RANDFILE $dir/private/.rand unique_subject no private_key $dir/private/root_ca.key certificate $dir/certs/root_ca.crt default_days 3650 default_crl_days 30 default_md sm3 preserve no email_in_dn no name_opt ca_default cert_opt ca_default copy_extensions copy x509_extensions v3_ca policy policy_anything [ policy_anything ] countryName optional stateOrProvinceName optional localityName optional organizationName optional organizationalUnitName optional commonName supplied emailAddress optional [ req ] default_bits 256 encrypt_key yes default_md sm3 string_mask utf8only distinguished_name req_distinguished_name x509_extensions v3_ca req_extensions v3_req [ req_distinguished_name ] [ v3_ca ] subjectKeyIdentifier hash authorityKeyIdentifier keyid:always,issuer basicConstraints critical, CA:true keyUsage critical, digitalSignature, cRLSign, keyCertSign [ v3_req ] basicConstraints CA:FALSE keyUsage nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage serverAuth, clientAuth subjectAltName alt_names [ alt_names ] DNS.1 localhost IP.1 127.0.0.1关键配置解读default_md sm3指定默认的摘要算法为SM3这是国密标准。[ v3_ca ]段定义了CA证书的扩展项。CA:true表明这是CA证书keyCertSign表示该证书可用于签发其他证书。[ v3_req ]段定义了证书请求CSR和最终证书的扩展项。serverAuth和clientAuth表明该证书可用于服务器或客户端认证。[ alt_names ]定义了主题备用名称SAN这里包含了localhost和127.0.0.1方便本地测试。在实际生产环境中你需要将其替换为真实的域名和IP。有了这个配置文件我们后续的命令就可以通过-config ./openssl.cnf参数来使用它确保行为一致。4. 生成SM2自签名根证书CA万事俱备现在开始创建我们信任链的基石——自签名根CA证书。4.1 生成SM2算法私钥首先为根CA生成一个SM2椭圆曲线参数下的私钥。SM2标准推荐使用素数域256位椭圆曲线在OpenSSL中对应的曲线名就是sm2。openssl ecparam -genkey -name sm2 -out root_ca/private/root_ca.key命令解析ecparam椭圆曲线参数操作命令。-genkey生成一个新的椭圆曲线密钥对。-name sm2指定使用SM2曲线。这是关键-out指定生成的私钥文件路径。执行后会在root_ca/private/目录下生成root_ca.key文件。这个文件是绝密的必须妥善保管你可以查看一下这个私钥的细节openssl ec -in root_ca/private/root_ca.key -text -noout输出会显示私钥类型是SM2以及椭圆曲线参数和私钥值通常不显示明文。4.2 创建自签名根证书接下来我们用刚生成的私钥为自己签发一张证书。这个过程是“自签名”的因为颁发者和主体都是我们自己。openssl req -new -x509 -days 3650 \ -key root_ca/private/root_ca.key \ -out root_ca/certs/root_ca.crt \ -config ./openssl.cnf \ -subj /CCN/STBeijing/LBeijing/OMy SM2 CA/CNMy Root CA命令解析req -new -x509-new表示生成新的证书请求-x509表示直接输出一个自签名的X.509证书而不是证书请求CSR。-days 3650证书有效期10年。-key指定用于签名的私钥文件。-out指定输出的证书文件。-config使用我们自定义的配置文件。-subj指定证书主题Subject信息避免了交互式提问。CCN国家中国。STBeijing省份北京。LBeijing城市北京。OMy SM2 CA组织名称。CNMy Root CA通用名称这里是CA的名称。重要提示-subj参数中的信息可以根据你的实际情况修改。CN(Common Name) 在CA证书中通常用于标识CA机构本身。4.3 验证根证书生成后务必验证一下证书内容确保一切符合预期。openssl x509 -in root_ca/certs/root_ca.crt -text -noout请重点关注以下部分Signature Algorithm应该显示sm2sign-with-sm3。这是SM2证书的核心标识。Public Key Algorithm应该显示id-ecPublicKey并且在下方的ASN1 OID中能看到SM2。Issuer和Subject应该是相同的即你在-subj中设置的信息。X509v3 extensions在扩展部分你应该能看到CA:TRUE和keyCertSign等关键属性表明这是一张CA证书。如果以上信息都正确那么你的SM2根CA证书就成功创建了你可以把这个root_ca.crt文件导入到系统或浏览器的信任根证书存储区这样由它签发的证书就会被自动信任。不过我们本地测试暂时用命令行验证即可。5. 生成SM2服务器证书服务器证书用于安装在Web服务器如Nginx、Apache上提供HTTPS服务。它需要由根CA证书签发。5.1 生成服务器SM2私钥为服务器生成独立的SM2私钥。openssl ecparam -genkey -name sm2 -out server/private/server.key5.2 创建证书签名请求CSRCSR文件包含了服务器的公钥和身份信息提交给CA进行签名。注意这里我们使用req -new但不加-x509。openssl req -new -key server/private/server.key \ -out server/server.csr \ -config ./openssl.cnf \ -subj /CCN/STBeijing/LBeijing/OMy Server Inc./CNsm2.demo.local关键点-key指定了上一步生成的服务器私钥。-subj中的CNsm2.demo.local是服务器的主机名Common Name。在HTTPS中浏览器会检查访问的域名是否与证书的CN或SAN匹配。我们这里用一个测试域名sm2.demo.local。你需要确保在测试机器的/etc/hosts文件中将其指向你的服务器IP如127.0.0.1 sm2.demo.local。5.3 使用根CA为CSR签名生成服务器证书现在扮演CA的角色用根CA的私钥对服务器的CSR进行签名。openssl ca -batch \ -in server/server.csr \ -out server/certs/server.crt \ -config ./openssl.cnf \ -extensions v3_req \ -days 365命令解析ca使用CA模式进行证书签发。-batch以批处理模式运行不进行交互式确认。-in输入CSR文件。-out输出签发的证书文件。-config同样使用我们的自定义配置。-extensions v3_req至关重要这个参数告诉OpenSSL在签发证书时应用配置文件中[ v3_req ]章节定义的扩展项。正是这个章节包含了serverAuth和subjectAltName使得这张证书能被识别为有效的服务器证书。-days 365设置服务器证书有效期为1年。执行成功后除了生成server.crt你还会发现root_ca/index.txt和root_ca/serial文件被更新了记录了这次签发行为。这就是一个简易的证书数据库。5.4 验证服务器证书同样我们需要验证生成的服务器证书。openssl x509 -in server/certs/server.crt -text -noout检查要点Signature Algorithm应为sm2sign-with-sm3且Issuer应该是你的根CA信息。SubjectCN应为sm2.demo.local。X509v3 extensions必须包含X509v3 Basic Constraints: CA:FALSE。必须包含X509v3 Key Usage和X509v3 Extended Key Usage: TLS Web Server Authentication。必须包含X509v3 Subject Alternative Name里面应该有DNS:localhost, IP Address:127.0.0.1。这是我们配置文件中[alt_names]部分的内容。对于现代浏览器和客户端SAN比CN更重要必须正确配置。最后验证证书链用根CA证书验证服务器证书是否由其签发。openssl verify -CAfile root_ca/certs/root_ca.crt server/certs/server.crt如果输出server/certs/server.crt: OK说明验证通过信任链完整。6. 生成SM2客户端证书可选对于双向TLS认证mTLS客户端也需要证书。步骤与服务器证书高度相似。6.1 生成客户端SM2私钥与CSR# 生成私钥 openssl ecparam -genkey -name sm2 -out client/private/client.key # 生成CSRCN可以标识客户端身份如用户ID或设备名 openssl req -new -key client/private/client.key \ -out client/client.csr \ -config ./openssl.cnf \ -subj /CCN/STBeijing/LBeijing/OMy Client/CNaliceexample.com6.2 使用根CA签发客户端证书openssl ca -batch \ -in client/client.csr \ -out client/certs/client.crt \ -config ./openssl.cnf \ -extensions v3_req \ -days 365注意这里我们仍然使用了-extensions v3_req因为它里面也定义了clientAuth。如果你需要纯客户端证书可以单独定义一个[ v3_client ]扩展段只包含clientAuth并在签发时指定-extensions v3_client。6.3 客户端证书格式转换PKCS#12客户端证书通常需要打包成PKCS#12格式.p12或.pfx包含私钥和证书链方便导入到浏览器或客户端应用中。openssl pkcs12 -export \ -inkey client/private/client.key \ -in client/certs/client.crt \ -certfile root_ca/certs/root_ca.crt \ -out client/client.p12 \ -password pass:YourClientPassword参数解释-inkey,-in指定客户端的私钥和证书。-certfile非常重要指定根CA证书。这样打包进去后就形成了完整的证书链客户端证书 - 根CA证书客户端在出示证书时能一并提供链方便服务端验证。-out输出PKCS#12文件。-password为PKCS#12文件设置密码请将YourClientPassword替换为强密码。现在client.p12文件就可以分发给客户端使用了。7. 常见问题、排查技巧与实战应用7.1 编译与算法支持问题问题1编译OpenSSL时enable-sm2等选项无效或报错。原因你使用的OpenSSL源码版本太旧可能早于国密算法被合并进上游的时间点大约在1.1.1后期版本。解决确保下载的是OpenSSL 1.1.1系列较新的版本如1.1.1w。可以到OpenSSL官网或GitHub仓库查看版本标签。问题2执行openssl ecparam -list_curves | grep -i sm2没有输出。原因编译时国密支持未成功开启或者你运行的openssl命令不是我们刚编译安装的那个。解决确认编译配置命令中包含了enable-sm2。使用绝对路径/usr/local/openssl-1.1.1w/bin/openssl执行命令。检查终端PATH环境变量确保我们添加的路径在系统路径之前。7.2 证书生成与验证问题问题3生成证书时Signature Algorithm显示为ecdsa-with-SHA256而不是sm2sign-with-sm3。原因这是最常见的问题。虽然密钥是SM2曲线生成的但默认的签名算法可能被设置为SHA256。关键在于签名时使用的摘要算法必须是SM3。解决确保配置文件正确检查你的openssl.cnf中[ CA_default ]和[ req ]区块下的default_md sm3是否设置。确保命令指定了正确的配置在openssl req和openssl ca命令中必须通过-config参数指向你修改过的配置文件。检查CA证书的签名根CA证书是自签名的如果它本身不是sm3那么它签发的下级证书也不会是。确保生成根证书时也使用了正确的配置。问题4openssl verify验证证书链失败提示unable to get local issuer certificate。原因openssl verify找不到签发者根CA证书。解决使用-CAfile参数明确指定根CA证书的路径如openssl verify -CAfile root_ca.crt server.crt。问题5浏览器访问HTTPS站点提示证书无效不是私密连接。原因浏览器不信任你的自签名根CA证书。解决仅测试环境将root_ca.crt文件导入到操作系统的“受信任的根证书颁发机构”存储区。具体步骤因操作系统和浏览器而异。更重要的原因确保证书的SANSubject Alternative Name包含了您访问网站时使用的确切域名或IP地址。现代浏览器对SAN的检查非常严格CN不匹配有时会被忽略但SAN不匹配一定会报错。7.3 在Nginx中配置SM2证书要让Nginx支持SM2需要国密算法套件。虽然Nginx原生不支持但可以通过Tongsuo铜锁或GMSSL这类支持国密的SSL库来编译Nginx。这里给出一个概念性步骤和配置示例。前提你需要先编译安装支持国密的Tongsuo库然后使用它来编译Nginx。这个过程较为复杂超出了本篇“证书生成”的核心范围但配置文件的思路是相通的。假设你已经有了一个支持国密的Nginx其nginx.conf中HTTPS服务器配置块可能如下server { listen 443 ssl; server_name sm2.demo.local; # 指定SM2服务器证书和私钥 ssl_certificate /path/to/your/sm2_certs_demo/server/certs/server.crt; ssl_certificate_key /path/to/your/sm2_certs_demo/server/private/server.key; # 指定国密密码套件例如 ECC-SM2-WITH-SM4-SM3 ssl_ciphers ECC-SM2-WITH-SM4-SM3:ECDHE-SM2-WITH-SM4-SM3; ssl_prefer_server_ciphers on; # 启用双向认证可选 # ssl_client_certificate /path/to/your/sm2_certs_demo/root_ca/certs/root_ca.crt; # ssl_verify_client on; ... # 其他配置 }关键点ssl_ciphers必须配置为国密套件否则Nginx可能会使用默认的RSA/ECC套件导致握手失败。7.4 使用OpenSSL s_client测试SM2 HTTPS服务在部署Nginx之前我们可以先用OpenSSL自带的s_client工具模拟客户端测试证书和私钥是否正确。# 启动一个简单的测试HTTPS服务例如用OpenSSL的s_server # 在一个终端运行服务端监听8443端口 openssl s_server -accept 8443 \ -cert server/certs/server.crt \ -key server/private/server.key \ -CAfile root_ca/certs/root_ca.crt \ -www # 在另一个终端用客户端连接测试 openssl s_client -connect localhost:8443 \ -servername sm2.demo.local \ -CAfile root_ca/certs/root_ca.crt如果连接成功在s_client的输出中你应该能看到“Certificate chain”并且验证返回 “Verify return code: 0 (ok)”。同时在“Cipher”一行如果服务端支持国密可能会显示相关的密码套件信息。这个测试能有效验证证书链和密钥的匹配性。7.5 证书链与中间证书我们的例子是简单的两级结构根CA - 服务器证书。在更复杂的生产环境中可能会引入中间CAIntermediate CA形成三级链根CA - 中间CA - 服务器证书。这样做的好处是根CA可以离线保存更加安全由中间CA在线签发证书。创建中间CA的流程是用根CA签发一个“中间CA证书”其扩展属性包含CA:TRUE, pathlen:0pathlen:0表示它不能再签发下级CA。然后用中间CA的私钥去签发服务器证书。在服务器配置中需要将服务器证书和中间CA证书合并成一个“证书链文件”cat server.crt intermediate.crt chain.crt提供给客户端。客户端需要信任根CA证书。其核心命令和原理与本文所述完全一致只是主题Subject和配置文件中的扩展属性需要稍作调整。理解了本文的基础构建多级证书链也就水到渠成了。整个流程走下来从编译支持国密的OpenSSL到规划目录、配置CA再到生成根证书、服务器证书和客户端证书最后进行验证和问题排查我们相当于亲手搭建了一个小型的国密PKI公钥基础设施实验环境。这不仅仅是学会了几个命令更重要的是理解了证书信任链、自签名CA的作用以及SM2算法在X.509证书中的具体体现。下次当你需要在生产环境中部署国密合规方案时这份从头到尾的实践经验就是最扎实的底气。