PKCS#10 证书签名请求(CSR)格式详解:RFC 2986 与国密 SM2 适配
概述
在 PKI(公钥基础设施)体系中,证书申请是一个关键流程。任何实体想要获得数字证书,都需要向 CA(证书颁发机构)提交一个证书签名请求(Certificate Signing Request,简称 CSR)。CSR 的本质是:申请者向 CA 证明"我是谁",并提供自己的公钥和身份信息,请求 CA 签发正式证书。
PKCS#10 是最广泛使用的 CSR 格式标准,定义于 RFC 2986(1999年发布,替代早期的 PKCS#10 v1.7)。它采用 ASN.1 DER 编码,结构清晰,支持扩展属性,成为 OpenSSL、Bouncy Castle、Go crypto/x509 等主流密码库的默认实现。
国密环境下,SM2 证书的 CSR 需要遵循 GM/T 0034-2014 和 GM/T 0015-2023 的约束,在 PKCS#10 框架上叠加国密 OID 和算法标识。
PKCS#10 数据结构
ASN.1 定义
根据 RFC 2986 Section 4,PKCS#10 的结构如下:
CertificateRequest ::= SEQUENCE {
certificationRequestInfo CertificationRequestInfo,
signatureAlgorithm AlgorithmIdentifier,
signature BIT STRING
}
CertificationRequestInfo ::= SEQUENCE {
version INTEGER {v1(0)},
subject Name,
subjectPKInfo SubjectPublicKeyInfo OPTIONAL,
attributes [0] Attributes OPTIONAL
}| 字段 | 类型 | 说明 |
|---|---|---|
version | INTEGER | 固定为 0(v1) |
subject | Name | 申请者的 X.509 RDN 名称 |
subjectPKInfo | SubjectPublicKeyInfo | 申请者的公钥信息 |
attributes | SET OF Attribute | 可选扩展属性 |
signatureAlgorithm | AlgorithmIdentifier | 签名算法标识 |
signature | BIT STRING | 对 certificationRequestInfo 的签名 |
核心字段详解
#### 1. subject(主体名称)
subject 是 CSR 中最关键的部分之一,决定了证书颁发后的 Subject 字段。它遵循 X.509 Name 结构,由相对可区分名(RDN)组成:
Name ::= CHOICE {
RDNSequence RDNSequence
}
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
AttributeTypeAndValue ::= SEQUENCE {
type AttributeType,
value AttributeValue
}常见 RDN 示例:
CN=example.com(通用名称)O=Example Organization(组织名称)OU=IT Department(组织单位)C=CN(国家)
subjectPKInfo 使用标准的 SubjectPublicKeyInfo 结构(RFC 5480 / X.509):
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}对于 SM2 算法,algorithm OID 为 1.2.156.10197.1.301(sm2p256v1),参数为空或曲线参数。
#### 3. attributes(扩展属性)
attributes 是 CSR 中可包含额外信息的可选字段。常见属性包括:
| 属性 OID | 名称 | 用途 |
|---|---|---|
1.2.840.113549.1.9.1 | challengePassword | 挑战密码(可选) |
1.2.840.113549.1.9.2 | unstructuredName | 未结构化名称 |
1.2.840.113556.1.4.195 | requestNonce | 请求随机数 |
| 属性 OID | 名称 | 用途 |
|---|---|---|
1.2.156.10197.1.501 | sm2sign_with_sm3 | SM2 签名算法 OID |
1.2.156.10197.1.301 | sm2p256v1 | SM2 曲线 OID |
CSR 生成流程
标准流程(OpenSSL 示例)
# 1. 生成 SM2 私钥
openssl ecparam -genkey -name sm2p256v1 -noout -out private_key.pem
# 2. 生成 CSR(包含完整的 subject 和扩展)
openssl req -new -key private_key.pem -out request.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=Example Corp/OU=IT/CN=test.example.com" \
-addext "subjectAltName=DNS:test.example.com,DNS:www.test.example.com"
# 3. 查看 CSR 内容
openssl req -in request.csr -text -nooutPython 生成 CSR(cryptography 库)
注意:gmssl 库不提供密钥生成 API,需要先用 OpenSSL 生成 SM2 密钥对,再用 gmssl 进行签名操作。
import subprocess
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from gmssl import sm2, func
# 1. 使用 OpenSSL 生成 SM2 密钥对
subprocess.run([
'openssl', 'ecparam', '-genkey', '-name', 'SM2', '-noout', '-out', 'private_key.pem'
], check=True)
# 2. 读取私钥(十六进制格式)
result = subprocess.run(['openssl', 'ec', '-in', 'private_key.pem', '-text', '-noout'],
capture_output=True, text=True)
lines = result.stdout.split('\n')
priv_hex = ''
pub_hex = ''
for line in lines:
if line.startswith('priv:'):
priv_hex = line.split(':')[1].replace(' ', '').replace('\n', '')
elif line.startswith('pub:'):
pub_hex = line.split(':')[1].replace(' ', '').replace('\n', '')
elif pub_hex and line.strip() and not line.strip().startswith(('ASN1', 'Public')):
pub_hex += line.strip().replace(' ', '')
# 移除公钥开头的 04 前缀(gmssl 不需要)
if pub_hex.startswith('04'):
pub_hex = pub_hex[2:]
# 3. 使用 gmssl 进行签名
crypt = sm2.CryptSM2(priv_hex, pub_hex)
message = b"test message"
signature = crypt.sign_with_sm3(message)
print(f"Signature: {signature}")
# 4. 验证签名
result = crypt.verify_with_sm3(signature, message)
print(f"Verify: {result}")国密 SM2 CSR 特殊要求
根据 GM/T 0034-2014 第 5.2 节,SM2 证书的 CSR 需要满足以下要求:
- 签名算法必须是 SM2WithSM3(OID
1.2.156.10197.1.501) - 公钥算法必须是 SM2(OID
1.2.156.10197.1.301) - 证书主题必须包含国密 CN 扩展(如适用)
- 必须支持双证书机制:签名证书和加密证书分别申请
CSR 验证流程
CA 收到 CSR 后,需要执行完整的验证流程:
CA 验证流程
─────────────────────────────────────────
1. 格式验证 检查 ASN.1 编码是否合法
检查必需字段是否存在
2. 签名验证 提取 subjectPKInfo 中的公钥
用该公钥验证 CSR 签名
确认 CSR 未被篡改
3. 身份信息验证 检查 subject 是否符合 CA 策略
检查 SAN(Subject Alternative Name)
验证域名所有权(DNS TXT 记录等)
4. 策略合规检查 检查 CSR 中的扩展是否符合 CA/B 论坛要求
检查 keyUsage 和 extendedKeyUsage
5. 签发证书 生成正式证书(有效期、序列号等)
签名并返回证书签名验证原理
CSR 的签名是用申请者的私钥对 certificationRequestInfo 字段(DER 编码)计算的哈希值。验证时:
- 解码 CSR 获取
certificationRequestInfo - 提取
subjectPKInfo中的公钥 - 用该公钥验证
signature字段
# Python 验证 CSR 签名
from cryptography import x509
from cryptography.hazmat.primitives import hashes
def verify_csr_signature(csr_pem: bytes) -> bool:
csr = x509.load_certificate_request_pem(csr_pem)
# 获取 CSR 的 TBS(To Be Signed)部分
tbs_certificate = csr.public_bytes(serialization.Encoding.DER)
# 提取签名和公钥
public_key = csr.public_key()
signature = csr.signature
# 验证签名
try:
public_key.verify(
signature,
tbs_certificate,
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except Exception as e:
print(f"Verification failed: {e}")
return False国密 CSR 验证(SM2)
from gmssl import sm2, func
def verify_sm2_csr(csr_pem: bytes, pubkey_hex: str, sig_hex: str) -> bool:
"""验证 SM2 CSR 签名"""
# 解码 CSR 获取 TBS 部分
from cryptography import x509
csr = x509.load_certificate_request_pem(csr_pem)
# 获取 TBS 的 DER 编码
tbs_der = csr.public_bytes(serialization.Encoding.DER)
# SM3 哈希
hash_result = func.bytes_to_list(tbs_der)
# SM2 验签(需要正确的公钥)
crypt = sm2.CryptSM2('', pubkey_hex) # 私钥为空表示只验签
return crypt.verify_with_sm3(sig_hex, bytes(hash_result))CSR 与证书的关系
| 特性 | CSR | 证书 |
|---|---|---|
| 用途 | 申请证书 | 证明身份 |
| 签名方 | 申请者私钥 | CA 私钥 |
| 有效期 | 无(一次性使用) | 有(通常 1-2 年) |
| 公开性 | 保密 | 公开分发 |
| 内容 | 公钥 + 身份信息 | 公钥 + 身份信息 + CA 签名 |
工程实践中的常见问题
问题 1:CSR 签名算法不匹配
现象:CA 拒绝 CSR,报"签名算法不支持"。
原因:CSR 使用的签名算法与 CA 策略不符。例如,使用 RSA-SHA256 签名但 CA 只接受 SM2-SM3。
解决方案:确保 CSR 签名算法与目标证书一致。
# 查看 CSR 的签名算法
openssl req -in request.csr -text -noout | grep "Signature Algorithm"问题 2:subject 字段格式错误
现象:CA 无法解析 CSR 中的身份信息。
原因:subject 的 RDN 顺序不符合 RFC 4514 规范,或使用了不支持的属性类型。
解决方案:按照标准格式书写 subject:
/C=CN/ST=Beijing/L=Beijing/O=Example Corp/OU=IT/CN=test.example.com问题 3:SAN(Subject Alternative Name)缺失
现象:证书签发后浏览器警告"域名不匹配"。
原因:CSR 中未包含 SAN 扩展,或 SAN 与 CN 不一致。
解决方案:在 CSR 生成时显式添加 SAN:
openssl req -new -key private_key.pem -out request.csr \
-subj "/CN=test.example.com" \
-addext "subjectAltName=DNS:test.example.com,DNS:www.test.example.com"问题 4:挑战密码(Challenge Password)处理
现象:CA 要求提供挑战密码,但 CSR 中未包含。
原因:部分 CA(尤其是 EV 证书)要求 CSR 中包含挑战密码属性。
解决方案:在 CSR 中添加挑战密码属性:
openssl req -new -key private_key.pem -out request.csr \
-subj "/CN=test.example.com" \
-addext "challengePassword=YourChallengePassword"国密 PKI 中的 CSR 实践
GM/T 0034-2014 要求
根据 GM/T 0034-2014《基于 SM2 密码算法的证书认证系统密码及其相关安全技术规范》,SM2 证书申请的 CSR 需要满足:
- 双证书申请:签名证书和加密证书必须分别申请 CSR
- 算法标识:CSR 签名算法必须是
sm2sign_with_sm3(OID1.2.156.10197.1.501) - 密钥用途:签名证书的
keyUsage必须包含digitalSignature,加密证书必须包含keyEncipherment - 扩展支持:支持国密扩展的 SAN 格式(如国密域名命名规则)
国密 CSR 生成示例(Python)
from gmssl import sm2, sm3
from gmssl.func import bytes_to_list
import struct
def generate_sm2_csr(private_key_hex: str, common_name: str) -> tuple:
"""生成 SM2 CSR"""
# 获取公钥
crypt = sm2.CryptSM2('', '')
public_key_hex = crypt.generate_public_key(private_key_hex)
# 构建 Subject
subject = f"/C=CN/ST=Beijing/O=Example Corp/CN={common_name}"
# 构建 CSR 的 TBS 部分(简化版,实际需要完整的 ASN.1 编码)
# 注意:生产环境应使用完整 ASN.1 编码器
tbs_der = build_sm2_csr_tbs(subject, public_key_hex)
# SM3 哈希
hash_result = sm3.sm3_hash(bytes_to_list(tbs_der))
# SM2 签名
signature = crypt.sign(tbs_der, private_key_hex)
# 构建完整的 CSR
csr = build_pkcs10_csr(tbs_der, signature)
return csr, public_key_hex
def build_sm2_csr_tbs(subject: str, public_key_hex: str) -> bytes:
"""构建 SM2 CSR 的 TBS 部分"""
# 这里省略完整的 ASN.1 编码实现
# 实际实现需要使用 asn1crypto 或类似库
pass
def build_pkcs10_csr(tbs_der: bytes, signature: bytes) -> bytes:
"""构建完整的 PKCS#10 CSR"""
# 组装 CertificationRequest
# 省略详细实现
pass国密 CSR 验证流程
def verify_sm2_csr(csr_pem: bytes) -> bool:
"""验证国密 CSR"""
from gmssl import sm2, sm3
from gmssl.func import bytes_to_list
# 1. 解析 CSR
csr = parse_csr(csr_pem)
# 2. 提取 TBS 和签名
tbs_der = csr['certificationRequestInfo']
signature = csr['signature']
# 3. 提取公钥
public_key_hex = csr['subjectPKInfo']['public_key']
# 4. 计算 SM3 哈希
hash_result = sm3.sm3_hash(bytes_to_list(tbs_der))
# 5. 验证签名
crypt = sm2.CryptSM2('', public_key_hex)
return crypt.verify(signature, hash_result)与其他 CSR 格式的对比
| 特性 | PKCS#10 | CMP (RFC 4210) | SCEP |
|---|---|---|---|
| 格式 | ASN.1 DER | ASN.1 DER | PKCS#7 封装 |
| 安全传输 | 无(需配合 TLS/HTTPS) | 内置安全机制 | HTTP POST |
| 自动化 | 手动提交 | 完整自动化 | 有限自动化 |
| 国密支持 | 原生支持(GM/T 0034) | 需扩展 | 不支持 |
| 应用场景 | 传统 CA 申请 | 企业级 PKI | 简单部署 |
总结
PKCS#10 CSR 是 PKI 体系中最基础的数据交换格式之一。理解其结构对于:
- 开发者:正确生成和处理证书申请
- CA 运营者:实现安全的证书签发流程
- 审计人员:验证证书申请的合规性
参考
- RFC 2986 - PKCS #10: Certification Request Standard
- RFC 5480 - Elliptic Curve Cryptography Subject Public Key Information
- GM/T 0034-2014 基于 SM2 密码算法的证书认证系统密码及其相关安全技术规范
- GM/T 0015-2023 数字证书格式
- OpenSSL CSR 生成指南