一、问题表现与根本原因
1.1 典型错误表现
国密CSR生成时,"Signature Algorithm"字段错误通常表现为以下几种情况:
签名算法OID不正确:CSR中显示的签名算法为 `1.2.840.10045.4.3.2`(SHA256withECDSA)或RSA相关OID,而非国密标准的 `1.2.156.10197.1.501`
公钥算法OID错误:SubjectPublicKeyInfo中算法为 `1.2.840.10045.2.1`(id-ecPublicKey),而非国密的 `1.2.156.10197.1.301`
Unknown named curve 错误:系统无法识别SM2曲线OID(`1.2.156.10197.1.301`)
CA校验失败:CSR提交给国密CA时因签名算法不匹配被拒绝
1.2 根本原因
国密SM2算法使用的OID体系与标准RSA/ECDSA完全不同,而多数开发工具和语言标准库默认不识别国密OID:
字段 标准RSA/ECDSA OID 国密SM2 OID 说明
签名算法 1.2.840.113549.1.1.11 (RSA) / 1.2.840.10045.4.3.2 (ECDSA) 1.2.156.10197.1.501 SM2 with SM3
公钥算法 1.2.840.10045.2.1 (id-ecPublicKey) 1.2.156.10197.1.301 SM2公钥
曲线OID 1.2.840.10045.3.1.7 (P-256) 1.2.156.10197.1.301 SM2曲线
核心问题:OpenSSL 1.1.1及部分版本默认不内置SM2曲线OID映射表,导致生成CSR时使用了错误的OID。
二、通用解决方案
2.1 必须使用支持国密的工具
OpenSSL官方版本不支持国密算法,直接用 `openssl` 命令生成国密证书会失败。必须使用以下替代方案:
工具 说明
GmSSL 国密专用开源工具,支持SM2/SM3/SM4
铜锁/Tongsuo 阿里等维护的国密增强OpenSSL分支
openHiTLS 华为等国密适配版本
商业信创发行版 华为OpenEuler的openssl-gm包等
2.2 GmSSL生成CSR的正确命令
使用GmSSL生成国密CSR时,必须指定 `-sm3` 签名算法和 `-sigopt` 参数:
```bash
# 1. 生成SM2私钥
gmssl ecparam -genkey -name sm2p256v1 -out sm2_private.key
# 2. 生成CSR(必须指定-sm3)
gmssl req -new -key sm2_private.key -out sm2.csr -sm3 -sigopt "distid:1234567812345678"
```
2.3 手动修正已生成的CSR
如果已经生成了错误的CSR,需要重写TBSCertificate中的算法字段:
重写 SubjectPublicKeyInfo.algorithm 为 SM2 公钥OID(`1.2.156.10197.1.301`)
同步更新签名算法OID 为 `1.2.156.10197.1.501`
三、各开发环境具体解决方案
3.1 Go语言
Go标准库 `crypto/x509` 默认不识别国密OID,需要**手动注册签名算法**:
```go
import "crypto/x509"
import "encoding/asn1"
func init() {
// 注册SM2签名算法OID
x509.RegisterSignatureAlgorithm(
x509.SM2WithSM3, // 自定义常量
x509.SignatureAlgorithm(0),
asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 501}, // SM2 OID
)
}
```
解析证书时还需注意:
Certificate.PublicKey 应为 `*sm2.PublicKey` 类型,而非 `*ecdsa.PublicKey`
需自定义 `UnmarshalPKIXPublicKey` 解析SM2公钥
3.2 Java / Bouncy Castle
Java标准库同样不识别SM2 OID,必须**显式注册Bouncy Castle Provider**:
```java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
```
获取签名实例时使用正确的算法名称:`"SM3withSM2"`
3.3 OpenSSL API(C语言)
使用OpenSSL API生成SM2 CSR时,需注意以下要点:
```c
// 创建SM2密钥上下文
pkctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SM2, NULL);
EVP_PKEY_keygen_init(pkctx);
EVP_PKEY_generate(pkctx, &pkey);
// 设置用户ID(SM2标准要求)
EVP_PKEY_CTX_set1_id(pkctx, SM2_DEFAULT_USER_ID, SM2_DEFAULT_USER_ID_LEN);
// 使用SM3签名
X509_REQ_sign(x509Req, pkey, EVP_sm3());
```
四、验证方法
生成CSR后,使用以下命令验证Signature Algorithm字段是否正确:
```bash
# 查看CSR详情,确认Signature Algorithm为 1.2.156.10197.1.501
gmssl req -in sm2.csr -noout -text
# 或使用openssl查看
openssl req -in sm2.csr -noout -text
```
正确输出应显示:
```
Signature Algorithm: sm2sign-with-sm3 (1.2.156.10197.1.501)
```
五、总结与最佳实践
1. 工具选择:使用GmSSL、铜锁/Tongsuo等支持国密的工具,而非原生OpenSSL
2. OID精确性:确保签名算法OID为 `1.2.156.10197.1.501`,公钥算法OID为 `1.2.156.10197.1.301`
3. Provider注册:Java环境必须注册Bouncy Castle Provider
4. Go环境扩展:Go需手动注册签名算法OID
5. 用户ID设置:SM2签名时需传入正确的用户ID(默认 `1234567812345678`)
6. 证书链一致性:证书外层的 `signatureAlgorithm` 与 `TBSCertificate.signature` 必须保持一致