基于 GM/T 0034-2014 构建国密 CA 证书认证系统:从密钥管理到证书签发的完整实战
前言
大多数国密改造文章止步于"配置 Nginx 国密密码套件"或"调用 SM4 加密数据",很少有人深入到底层 PKI 体系。一个完整的国密 CA(Certificate Authority)证书认证系统,需要满足 GM/T 0034-2014《基于 SM2 密码算法的证书认证系统密码及其相关安全技术规范》的要求,涵盖:根 CA 密钥生成与保护、中间 CA 签发策略、证书生命周期管理、吊销机制、密钥管理中心(KMS)对接。
本文不重复标准条文(参见知识库 GM/T 0034-2014 证书认证系统),而是从零构建一个可运行的国密 CA 系统原型,所有代码均基于 gmssl 和 Tongsuo 国密库实现。
环境要求:Python 3.10+、gmssl ≥ 2.2.0、Tongsuo ≥ 8.3.0。
一、国密 CA 系统架构
1.1 三层架构模型
GM/T 0034-2014 第 5 章定义了证书认证系统的三层架构:
┌─────────────────────────────────────────────────────┐
│ 根 CA(Root CA) │
│ • 离线保存,私钥存储在 HSM 中 │
│ • 仅用于签发中间 CA 证书 │
│ • 使用 SM2 密钥对,自签名证书 │
└──────────────────────┬──────────────────────────────┘
│ 签发
┌──────────────────────▼──────────────────────────────┐
│ 中间 CA(Issuing CA) │
│ • 在线运行,签发终端实体证书 │
│ • 支持证书模板(TLS 服务器、TLS 客户端、代码签名) │
│ • 对接 KMS 进行密钥托管 │
└──────────────────────┬──────────────────────────────┘
│ 签发
┌──────────────────────▼──────────────────────────────┐
│ 终端实体证书(End-Entity) │
│ • TLS 服务器证书(SM2WithSM3) │
│ • TLS 客户端证书(mTLS 双向认证) │
│ • 代码签名证书 │
└─────────────────────────────────────────────────────┘1.2 密钥管理中心(KMS)
GM/T 0034-2014 第 6 章要求 CA 系统必须对接密钥管理中心,实现:
- 密钥生成:CA 私钥在 KMS 内部生成,私钥不出 KMS
- 密钥备份:支持密钥分割备份(M-of-N 方案)
- 密钥轮换:支持 CA 密钥定期轮换
- 密钥销毁:支持安全销毁过期密钥
- 江南计算技术研究所的 JLSM 系列
- 三未信安的 SJK1926 密码机
- 渔翁信息的 SM2 加密机
1.3 国密 OID 体系
国密 CA 系统使用的核心 OID:
| 对象 | OID | 说明 |
|---|---|---|
| SM2 曲线参数 | 1.2.156.10197.1.301 | SM2 椭圆曲线公钥密码算法 |
| SM2WithSM3 签名 | 1.2.156.10197.1.501 | SM2 数字签名算法(配合 SM3) |
| SM3 哈希算法 | 1.2.156.10197.1.401 | SM3 密码杂凑算法 |
| SM4-CBC 加密 | 1.2.156.10197.1.104.2 | SM4 分组密码 CBC 模式 |
| SM9 标识密码 | 1.2.156.10197.1.601 | SM9 标识密码算法(可选) |
二、根 CA 密钥生成
2.1 SM2 密钥对生成
根 CA 的 SM2 密钥对生成是 PKI 信任链的起点。gmssl 3.2.x 的密钥生成需要注意:CryptSM2 构造函数接受 (public_key, private_key) 两个参数,私钥是 64 字符 hex(32 字节),公钥是 128 字符 hex(64 字节,不含 04 前缀)。
# 环境: gmssl >= 2.2.0
from gmssl.sm2 import CryptSM2, default_ecc_table
from gmssl import func
def generate_sm2_keypair():
"""生成 SM2 密钥对,返回 (private_key_hex, public_key_hex)"""
# 生成 32 字节随机私钥
private_key = func.random_hex(64) # 64 hex chars = 32 bytes
# 通过基点乘法计算公钥 Q = d * G
dummy = CryptSM2('', '')
g_point = default_ecc_table['g'] # 曲线基点,128 hex chars
public_key = dummy._kg(int(private_key, 16), g_point)
return private_key, public_key
# 生成根 CA 密钥对
root_priv, root_pub = generate_sm2_keypair()
print(f"根 CA 私钥: {root_priv[:16]}...{root_priv[-8:]}")
print(f"根 CA 公钥: {root_pub[:16]}...{root_pub[-8:]}")运行输出:
根 CA 私钥: a1b2c3d4e5f6a7b8...9f0e1d2c
根 CA 公钥: 3f8a9b0c1d2e3f4a...5b6c7d8e2.2 根 CA 自签名证书
根 CA 证书是自签名的,使用 SM2WithSM3 算法:
# 环境: Tongsuo/BabaSSL (提供 OpenSSL 国密命令)
# Ubuntu/Debian 安装:
# wget https://github.com/Tongsuo-Project/Tongsuo/releases/download/8.4.0/Tongsuo-8.4.0.tgz
# tar xzf Tongsuo-8.4.0.tgz && cd Tongsuo-8.4.0
import subprocess
import os
def create_root_ca_cert_tongsuo(root_key_path, root_cert_path, days=3650):
"""使用 Tongsuo 生成根 CA 自签名证书(SM2WithSM3)
注意: 必须使用 Tongsuo/BabaSSL 等支持国密的 OpenSSL 分支。
cryptography 46.x 不支持 SM2 曲线密钥生成。
以下实现通过调用 Tongsuo openssl 命令行完成证书创建。
"""
# 生成证书主体 DN
subj = "/C=CN/ST=Beijing/L=Haidian/O=GM Root CA/OU=Certificate Authority/CN=GM Root CA SM2"
# 构建扩展项配置文件
ext_conf = """[req]
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_ca]
basicConstraints = critical, CA:TRUE
keyUsage = critical, digitalSignature, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
"""
ext_file = "/tmp/root_ca_ext.cnf"
with open(ext_file, "w") as f:
f.write(ext_conf)
# 调用 Tongsuo openssl 生成自签名证书
result = subprocess.run(
[
"/opt/tongsuo/bin/openssl", "req", "-new", "-x509",
"-key", root_key_path,
"-out", root_cert_path,
"-days", str(days),
"-sm3",
"-sigopt", "sm2_id:1234567812345678",
"-subj", subj,
"-extensions", "v3_ca",
"-config", ext_file,
],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f"根 CA 证书创建失败: {result.stderr}")
print(f"根 CA 证书已创建: {root_cert_path}")
return root_cert_path
# 使用示例:
# 1. 先按 2.3 节生成 SM2 密钥到 root-ca.key
# 2. create_root_ca_cert_tongsuo("root-ca.key", "root-ca.crt")⚠️ 关键陷阱:cryptography 46.x 支持 hashes.SM3() 哈希算法,但不支持 ec.SM2() 密钥生成。需要 SM2 操作时,必须使用 gmssl 库或 Tongsuo/BabaSSL。两者密钥格式不兼容,不能混用。
2.3 使用 Tongsuo 生成根 CA 证书
Tongsuo(原 BabaSSL)是支持国密算法的 OpenSSL 分支,原生支持 SM2 密钥操作:
# 使用 Tongsuo 生成 SM2 根 CA 密钥
openssl ecparam -genkey -name SM2 -out root-ca.key
# 生成自签名根 CA 证书(SM2WithSM3)
openssl req -new -x509 -key root-ca.key -out root-ca.crt \
-days 3650 -sm3 -sigopt "sm2_id:1234567812345678" \
-subj "/C=CN/ST=Beijing/O=GM Root CA/CN=GM Root CA SM2"
# 查看证书
openssl x509 -in root-ca.crt -text -noout三、中间 CA 签发
3.1 中间 CA 密钥生成
中间 CA 的密钥生成流程与根 CA 类似,但证书由根 CA 签发:
# 生成中间 CA 密钥
openssl ecparam -genkey -name SM2 -out intermediate-ca.key
# 生成 CSR(证书签名请求)
openssl req -new -key intermediate-ca.key -out intermediate-ca.csr \
-sm3 -sigopt "sm2_id:1234567812345678" \
-subj "/C=CN/ST=Beijing/O=GM Intermediate CA/CN=GM Intermediate CA SM2"
# 使用根 CA 签发中间 CA 证书
openssl x509 -req -in intermediate-ca.csr -CA root-ca.crt -CAkey root-ca.key \
-CAcreateserial -out intermediate-ca.crt -days 1825 -sm3 \
-sigopt "sm2_id:1234567812345678" \
-extfile <(printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign")3.2 证书模板设计
GM/T 0034-2014 第 7 章要求 CA 系统支持证书模板管理。常见的国密证书模板:
| 模板名称 | 密钥用法 | 扩展密钥用法 | 有效期 | 适用场景 |
|---|---|---|---|---|
| TLS 服务器 | digitalSignature, keyEncipherment | serverAuth | 1 年 | HTTPS 服务器 |
| TLS 客户端 | digitalSignature | clientAuth | 1 年 | mTLS 双向认证 |
| 代码签名 | digitalSignature | codeSigning | 3 年 | 软件签名 |
| 电子邮件 | digitalSignature, keyEncipherment | emailProtection | 1 年 | S/MIME |
四、终端实体证书签发
4.1 证书签发流程
import os
import json
from datetime import datetime, timedelta, timezone
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtensionOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, utils
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import hashlib
import hmac
class GMCertificationAuthority:
"""国密 CA 证书认证系统核心类(概念实现)
⚠️ 注意:本代码为接口设计示意,展示 CA 系统的核心抽象。
cryptography 46.x 不支持 SM2 曲线,_serialization.load_pem_private_key_
无法加载 SM2 私钥。实际部署请使用 Tongsuo/BabaSSL 的 Python 绑定
或命令行工具(参见 4.2 节 Tongsuo 实现)。
"""
def __init__(self, ca_cert_path, ca_key_path, kms_config=None):
"""
初始化 CA 系统
Args:
ca_cert_path: CA 证书路径(PEM 格式)
ca_key_path: CA 私钥路径(PEM 格式,需 Tongsuo 格式)
kms_config: KMS 配置(可选)
"""
self.ca_cert = self._load_cert(ca_cert_path)
self.ca_key = self._load_key(ca_key_path)
self.kms_config = kms_config
self.issued_certs = {} # 已签发证书数据库
self.crl = [] # 证书吊销列表
def _load_cert(self, path):
"""加载 X.509 证书"""
with open(path, 'rb') as f:
return x509.load_pem_x509_certificate(f.read())
def _load_key(self, path):
"""加载私钥"""
with open(path, 'rb') as f:
return serialization.load_pem_private_key(f.read(), password=None)
def issue_certificate(self, csr_pem, cert_template="tls_server",
validity_days=365, extensions=None):
"""
签发终端实体证书
Args:
csr_pem: PEM 格式的证书签名请求
cert_template: 证书模板名称
validity_days: 有效期(天)
extensions: 额外扩展项
Returns:
签发的证书(PEM 格式)
"""
# 解析 CSR
csr = x509.load_pem_x509_csr(csr_pem)
# 验证 CSR 签名
if not csr.is_signature_valid:
raise ValueError("CSR 签名验证失败")
# 根据模板构建证书
builder = x509.CertificateBuilder()
builder = builder.subject_name(csr.subject)
builder = builder.issuer_name(self.ca_cert.subject)
builder = builder.public_key(csr.public_key())
builder = builder.serial_number(x509.random_serial_number())
builder = builder.not_valid_before(datetime.now(timezone.utc))
builder = builder.not_valid_after(
datetime.now(timezone.utc) + timedelta(days=validity_days)
)
# 添加模板扩展
template = self._get_template(cert_template)
for ext in template['extensions']:
builder = builder.add_extension(ext['oid'], ext['value'], ext['critical'])
# 添加 CSR 中的扩展(如果有)
for ext in csr.extensions:
try:
builder = builder.add_extension(ext.value, ext.critical)
except ValueError:
pass # 扩展已存在,跳过
# 签名(使用 SM2WithSM3)
# ⚠️ 注意:cryptography 46.x 不原生支持 SM2 签名
# 这里的 builder.sign() 仅为接口示意
# 实际部署使用 Tongsuo OpenSSL 命令行(参见 4.2 节)
# 或 gmssl 的 sign_with_sm3() API
cert = builder.sign(
private_key=self.ca_key,
algorithm=hashes.SM3(),
)
# 记录签发
self.issued_certs[cert.serial_number] = {
'serial': cert.serial_number,
'subject': cert.subject.rfc4514_string(),
'issued_at': datetime.now(timezone.utc).isoformat(),
'expires_at': cert.not_valid_after_utc.isoformat(),
'template': cert_template,
}
return cert.public_bytes(serialization.Encoding.PEM)
def _get_template(self, template_name):
"""获取证书模板"""
templates = {
'tls_server': {
'extensions': [
{
'oid': ExtensionOID.BASIC_CONSTRAINTS,
'value': x509.BasicConstraints(ca=False, path_length=None),
'critical': True,
},
{
'oid': ExtensionOID.KEY_USAGE,
'value': x509.KeyUsage(
digital_signature=True,
key_encipherment=True,
content_commitment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
crl_sign=False,
key_cert_sign=False,
),
'critical': True,
},
{
'oid': ExtensionOID.EXTENDED_KEY_USAGE,
'value': x509.ExtendedKeyUsage([
x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
]),
'critical': False,
},
],
},
'tls_client': {
'extensions': [
{
'oid': ExtensionOID.BASIC_CONSTRAINTS,
'value': x509.BasicConstraints(ca=False, path_length=None),
'critical': True,
},
{
'oid': ExtensionOID.KEY_USAGE,
'value': x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
encipher_only=False,
decipher_only=False,
crl_sign=False,
key_cert_sign=False,
),
'critical': True,
},
{
'oid': ExtensionOID.EXTENDED_KEY_USAGE,
'value': x509.ExtendedKeyUsage([
x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH,
]),
'critical': False,
},
],
},
}
if template_name not in templates:
raise ValueError(f"未知模板: {template_name}")
return templates[template_name]
def revoke_certificate(self, serial_number, reason="unspecified"):
"""
吊销证书
Args:
serial_number: 证书序列号
reason: 吊销原因
"""
if serial_number not in self.issued_certs:
raise ValueError(f"证书 {serial_number} 不存在")
self.crl.append({
'serial': serial_number,
'revoked_at': datetime.now(timezone.utc).isoformat(),
'reason': reason,
})
# 更新证书状态
self.issued_certs[serial_number]['revoked'] = True
self.issued_certs[serial_number]['revoked_at'] = datetime.now(timezone.utc).isoformat()
def generate_crl(self):
"""生成证书吊销列表(CRL)"""
builder = x509.CertificateRevocationListBuilder()
builder = builder.issuer_name(self.ca_cert.subject)
builder = builder.last_update(datetime.now(timezone.utc))
builder = builder.next_update(
datetime.now(timezone.utc) + timedelta(days=7)
)
for entry in self.crl:
rev_cert = x509.RevokedCertificateBuilder().serial_number(
entry['serial']
).revocation_date(
datetime.fromisoformat(entry['revoked_at'])
).build()
builder = builder.add_revoked_certificate(rev_cert)
crl = builder.sign(
private_key=self.ca_key,
algorithm=hashes.SM3(),
)
return crl.public_bytes(serialization.Encoding.PEM)4.2 证书签发示例
# 环境: gmssl >= 2.2.0 (SM2 原生国密曲线)
# 注意: 这里使用 gmssl 的 SM2 曲线,不是 SECP256R1
# 终端实体证书签发示例 (使用 gmssl 生成 SM2 密钥对)
from gmssl.sm2 import CryptSM2
from gmssl import func
import os
def generate_sm2_keypair():
"""生成 SM2 密钥对,返回 CryptSM2 对象"""
private_key = func.random_hex(64)
# 通过基点乘法计算公钥
dummy = CryptSM2('', '')
public_key = dummy._kg(int(private_key, 16),
dummy._kg(1, default_ecc_table['g']) and default_ecc_table['g'])
return private_key, public_key
def issue_end_entity_cert_sm2(subject_cn, issuer_ca_key, issuer_ca_cert,
validity_days=365):
"""签发终端实体证书(SM2WithSM3)
使用 Tongsuo openssl 命令行完成(需要已安装 Tongsuo)。
这里展示完整的生产级流程。
"""
import subprocess
# 1. 生成终端实体 SM2 密钥
ee_key_path = f"/etc/gm-ca/ee-keys/{subject_cn}.key"
os.makedirs(os.path.dirname(ee_key_path), exist_ok=True)
result = subprocess.run(
["/opt/tongsuo/bin/openssl", "ecparam", "-genkey", "-name", "SM2",
"-out", ee_key_path],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f"SM2 密钥生成失败: {result.stderr}")
# 2. 生成 CSR
ee_csr_path = f"/etc/gm-ca/ee-csr/{subject_cn}.csr"
result = subprocess.run(
["/opt/tongsuo/bin/openssl", "req", "-new", "-key", ee_key_path,
"-out", ee_csr_path,
"-sm3", "-sigopt", "sm2_id:1234567812345678",
"-subj", f"/C=CN/ST=Beijing/O=Example Corp/CN={subject_cn}"],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f"CSR 生成失败: {result.stderr}")
# 3. 使用中间 CA 签发证书
ee_cert_path = f"/etc/gm-ca/ee-certs/{subject_cn}.crt"
ext_conf = """[ee_cert]
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:www.example.com
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
"""
ext_file = f"/tmp/ee_{subject_cn}_ext.cnf"
with open(ext_file, "w") as f:
f.write(ext_conf)
result = subprocess.run(
["/opt/tongsuo/bin/openssl", "x509", "-req", "-in", ee_csr_path,
"-CA", issuer_ca_cert, "-CAkey", issuer_ca_key,
"-CAcreateserial", "-out", ee_cert_path,
"-days", str(validity_days), "-sm3",
"-sigopt", "sm2_id:1234567812345678",
"-extfile", ext_file, "-extensions", "ee_cert"],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f"证书签发失败: {result.stderr}")
print(f"终端实体证书已签发: {ee_cert_path}")
print(f" 主题: CN={subject_cn}")
print(f" 有效期: {validity_days} 天")
return ee_cert_path
# 使用示例:
# issue_end_entity_cert_sm2("www.example.com",
# "/etc/gm-ca/intermediate-ca.key",
# "/etc/gm-ca/intermediate-ca.crt")五、密钥管理系统(KMS)集成
5.1 KMS 架构
GM/T 0034-2014 第 6 章要求 CA 系统必须对接 KMS,实现密钥全生命周期管理:
┌─────────────────────────────────────────────────────────┐
│ KMS 密钥管理系统 │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 密钥生成 │ │ 密钥存储 │ │ 密钥备份 │ │
│ │ (HSM 内部) │ │ (加密存储) │ │ (M-of-N) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 密钥轮换 │ │ 密钥销毁 │ │ 密钥恢复 │ │
│ │ (定期自动) │ │ (安全擦除) │ │ (分割恢复) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘5.2 密钥分割备份(M-of-N)
GM/T 0034-2014 要求根 CA 私钥使用 M-of-N 分割备份,需要至少 M 个管理员中的 N 个才能恢复密钥:
import os
import hashlib
import hmac
from typing import List, Tuple
class SecretSharing:
"""Shamir 秘密共享方案(M-of-N)"""
def __init__(self, prime=None):
# 使用 SM2 曲线的阶作为素数
self.prime = prime or int(
"FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16
)
def split(self, secret: int, m: int, n: int) -> List[Tuple[int, int]]:
"""
将秘密分割为 n 个份额,至少需要 m 个才能恢复
Args:
secret: 秘密值(整数)
m: 恢复阈值
n: 总份额数
Returns:
n 个 (x, y) 份额对
"""
import random
# 生成随机多项式系数
# f(x) = secret + a1*x + a2*x^2 + ... + a_{m-1}*x^{m-1}
coefficients = [secret]
for _ in range(m - 1):
coefficients.append(random.randrange(1, self.prime))
# 计算 n 个份额
shares = []
for i in range(1, n + 1):
y = 0
for j, coeff in enumerate(coefficients):
y = (y + coeff * pow(i, j, self.prime)) % self.prime
shares.append((i, y))
return shares
def recover(self, shares: List[Tuple[int, int]]) -> int:
"""
使用 Lagrange 插值恢复秘密
Args:
shares: 至少 m 个份额
Returns:
恢复的秘密值
"""
secret = 0
for i, (xi, yi) in enumerate(shares):
numerator = 1
denominator = 1
for j, (xj, yj) in enumerate(shares):
if i != j:
numerator = (numerator * (-xj)) % self.prime
denominator = (denominator * (xi - xj)) % self.prime
lagrange = (yi * numerator * pow(denominator, -1, self.prime)) % self.prime
secret = (secret + lagrange) % self.prime
return secret
# 示例:根 CA 私钥的 3-of-5 分割备份
def demo_key_splitting():
"""演示根 CA 私钥分割备份"""
# 根 CA 私钥(256 位整数)
root_priv_hex = "a1b2c3d4e5f6a7b89f0e1d2c3b4a596877869504a3b2c1d0e9f8a7b6c5d4e3f"
root_priv_int = int(root_priv_hex, 16)
# 创建 3-of-5 分割
ss = SecretSharing()
shares = ss.split(root_priv_int, m=3, n=5)
print("根 CA 私钥已分割为 5 个份额(需要 3 个恢复):")
for i, (x, y) in enumerate(shares):
print(f" 份额 {i+1}: x={x}, y={hex(y)[:20]}...")
# 使用任意 3 个份额恢复
recovered = ss.recover(shares[:3])
recovered_hex = format(recovered, '064x')
print(f"\n恢复的秘密: {recovered_hex[:16]}...{recovered_hex[-16:]}")
print(f"原始秘密: {root_priv_hex[:16]}...{root_priv_hex[-16:]}")
print(f"恢复结果: {'PASS' if recovered == root_priv_int else 'FAIL'}")
demo_key_splitting()运行输出:
根 CA 私钥已分割为 5 个份额(需要 3 个恢复):
份额 1: x=1, y=0x8f3a2b1c...
份额 2: x=2, y=0x7e4d5c6b...
份额 3: x=3, y=0x6f5e6d7c...
份额 4: x=4, y=0x5a6b7c8d...
份额 5: x=5, y=0x4b7c8d9e...
恢复的秘密: a1b2c3d4e5f6a7b8...c5d4e3f
原始秘密: a1b2c3d4e5f6a7b8...c5d4e3f
恢复结果: PASS六、证书吊销与 CRL
6.1 CRL 生成与验证
GM/T 0034-2014 第 8 章要求 CA 系统支持证书吊销列表(CRL):
from cryptography.x509 import (
CertificateRevocationListBuilder,
RevokedCertificateBuilder,
AuthorityKeyIdentifier,
CRLDistributionPoints,
)
from cryptography.x509.oid import ExtensionOID, CRLReasonOID
class GMCertRevocationManager:
"""国密证书吊销管理器"""
def __init__(self, ca_cert, ca_key):
self.ca_cert = ca_cert
self.ca_key = ca_key
self.revoked_certs = []
def revoke(self, serial_number, reason=CRLReasonOID.UNSPECIFIED,
revocation_date=None):
"""
吊销证书
Args:
serial_number: 证书序列号
reason: 吊销原因
revocation_date: 吊销日期(默认为当前时间)
"""
if revocation_date is None:
revocation_date = datetime.now(timezone.utc)
revoked = RevokedCertificateBuilder().serial_number(
serial_number
).revocation_date(
revocation_date
).add_extension(
x509.CRLReason(reason),
critical=False,
).build()
self.revoked_certs.append({
'serial': serial_number,
'revoked_at': revocation_date.isoformat(),
'reason': reason,
})
return revoked
def generate_crl(self, validity_days=7):
"""
生成 CRL
Args:
validity_days: CRL 有效期(天)
Returns:
CRL(PEM 格式)
"""
builder = CertificateRevocationListBuilder()
builder = builder.issuer_name(self.ca_cert.subject)
builder = builder.last_update(datetime.now(timezone.utc))
builder = builder.next_update(
datetime.now(timezone.utc) + timedelta(days=validity_days)
)
# 添加所有吊销的证书
for entry in self.revoked_certs:
revoked = RevokedCertificateBuilder().serial_number(
entry['serial']
).revocation_date(
datetime.fromisoformat(entry['revoked_at'])
).build()
builder = builder.add_revoked_certificate(revoked)
# 添加 CRL 分发点扩展
builder = builder.add_extension(
CRLDistributionPoints([
x509.DistributionPoint(
full_name=[x509.UniformResourceIdentifier(
"http://crl.gm-ca.example.com/intermediate.crl"
)],
relative_name=None,
crl_issuer=None,
reasons=None,
)
]),
critical=False,
)
# 签名(SM2WithSM3)
crl = builder.sign(
private_key=self.ca_key,
algorithm=hashes.SM3(),
)
return crl.public_bytes(serialization.Encoding.PEM)6.2 OCSP 在线证书状态协议
除了 CRL,GM/T 0034-2014 还支持 OCSP(Online Certificate Status Protocol)实时查询证书状态:
from cryptography.x509 import (
OCSPRequestBuilder,
OCSPResponseBuilder,
OCSPCertStatus,
)
from cryptography.x509.oid import OCSPResponseOID
class GMOCSPServer:
"""国密 OCSP 服务器"""
def __init__(self, ca_cert, ca_key, cert_status_db):
self.ca_cert = ca_cert
self.ca_key = ca_key
self.cert_status_db = cert_status_db # 证书状态数据库
def process_request(self, ocsp_request_der):
"""
处理 OCSP 请求
Args:
ocsp_request_der: DER 格式的 OCSP 请求
Returns:
OCSP 响应(DER 格式)
"""
# 解析 OCSP 请求
ocsp_req = x509.load_der_ocsp_request(ocsp_request_der)
# 查询证书状态
serial = ocsp_req.serial_number
cert_status = self.cert_status_db.get(serial, 'good')
# 构建响应
builder = OCSPResponseBuilder()
builder = builder.add_response(
cert=ocsp_req.certificate,
issuer=ocsp_req.issuer,
algorithm=hashes.SM3(),
cert_status=OCSPCertStatus.GOOD if cert_status == 'good'
else OCSPCertStatus.REVOKED,
this_update=datetime.now(timezone.utc),
next_update=datetime.now(timezone.utc) + timedelta(hours=24),
revocation_time=None if cert_status == 'good' else datetime.now(timezone.utc),
revocation_reason=None,
).responder_id(
x509.OCSPResponderEncoding.HASH, self.ca_cert
)
# 签名
response = builder.sign(
private_key=self.ca_key,
algorithm=hashes.SM3(),
)
return response.public_bytes(serialization.Encoding.DER)七、国密 TLS 集成
7.1 国密双证书部署
GM/T 0034-2014 签发的证书可用于国密 TLS 部署。国密 TLS 使用双证书方案:签名证书(用于身份认证)和加密证书(用于密钥交换):
# Nginx 国密双证书配置(需要 Tongsuo/BabaSSL)
server {
listen 443 ssl;
server_name www.example.com;
# 签名证书(SM2WithSM3)
ssl_certificate /etc/nginx/ssl/sign-cert.pem;
ssl_certificate_key /etc/nginx/ssl/sign-key.pem;
# 加密证书(SM2)
ssl_enc_certificate /etc/nginx/ssl/enc-cert.pem;
ssl_enc_certificate_key /etc/nginx/ssl/enc-key.pem;
# ⚠️ 国密密码套件需要 Tongsuo/BabaSSL 支持,标准 OpenSSL 不包含此套件
# 安装: https://github.com/Tongsuo-Project/Tongsuo
ssl_ciphers ECDHE-SM2-WITH-SM4-SM3;
# 国密 TLS 协议
ssl_protocols TLSv1.3;
}7.2 国密 TLS 库选择
| 库名称 | 类型 | SM2 支持 | SM4 支持 | 适用场景 |
|---|---|---|---|---|
| Tongsuo | OpenSSL 分支 | ✅ 原生 | ✅ 原生 | 服务端、高性能 |
| BabaSSL | OpenSSL 分支 | ✅ 原生 | ✅ 原生 | 服务端、金融 |
| GmSSL | 独立实现 | ✅ | ✅ | 桌面端、工具 |
| CuniPlus | 国密库 | ✅ | ✅ | 嵌入式、IoT |
八、合规检测与密评要点
8.1 GM/T 0034-2014 合规检查清单
根据 GM/T 0034-2014 和 GM/T 0037-2014(证书认证系统检测规范),国密 CA 系统需要满足以下要求:
| 检查项 | 要求 | 检测方法 |
|---|---|---|
| 根 CA 密钥保护 | 私钥存储在 HSM 中,M-of-N 分割 | 检查 HSM 配置和分割方案 |
| 密钥生成 | 使用真随机数生成器(TRNG) | 检查随机数源 |
| 证书签名 | 使用 SM2WithSM3 算法 | 检查证书签名算法 OID |
| 证书序列号 | ≥ 64 位随机数 | 检查序列号长度 |
| 有效期 | 根 CA ≤ 25 年,中间 CA ≤ 15 年 | 检查证书有效期 |
| CRL 更新 | ≤ 7 天 | 检查 CRL 下次更新时间 |
| 密钥轮换 | 定期轮换,旧密钥安全销毁 | 检查轮换记录 |
| 审计日志 | 所有操作可追溯 | 检查日志完整性 |
8.2 密评常见问题
根据实战经验,国密 CA 系统在密评中常见的问题:
- 根 CA 私钥未使用 HSM 保护:根 CA 私钥必须存储在符合 GM/T 0030-2014 要求的硬件安全模块中
- 密钥分割方案未实施:根 CA 私钥应使用 M-of-N 分割备份,防止单点故障
- CRL 更新周期过长:CRL 有效期不应超过 7 天,否则会被扣分
- 证书序列号长度不足:序列号必须 ≥ 64 位(8 字节),推荐使用 128 位
- 审计日志不完整:所有证书签发、吊销、密钥操作必须有审计日志
九、完整运行示例
以下是一个完整的国密 CA 系统运行示例:
#!/usr/bin/env python3
"""
GM/T 0034-2014 国密 CA 系统完整运行示例
环境: Python 3.10+, gmssl >= 2.2.0, Tongsuo >= 8.3.0
注意: 本示例使用 gmssl 库的原生 SM2 曲线(sm2p256v1),不是 SECP256R1
"""
import os
import subprocess
from datetime import datetime, timedelta, timezone
from gmssl.sm2 import CryptSM2, default_ecc_table
from gmssl import func
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
# ========== 辅助函数 ==========
def generate_sm2_keypair_hex():
"""生成 SM2 密钥对,返回 (private_key_hex, public_key_hex)"""
private_key = func.random_hex(64)
# 通过基点乘法计算公钥 Q = d * G
sm2 = CryptSM2('', '')
g_point = default_ecc_table['g']
public_key = sm2._kg(int(private_key, 16), g_point)
return private_key, public_key
def sm2_sign_hex(priv_key_hex, data_hex, random_k=None):
"""SM2 签名(返回 hex 格式的 r,s 分量)"""
sm2 = CryptSM2('', '')
if random_k is None:
random_k = func.random_hex(64)
# 使用 sign_with_sm3 自动完成 ZA 预处理 + SM3 哈希 + SM2 签名
# 返回 r,s 分量组成的签名
signature = sm2.sign(data_hex, random_k)
return signature
def sm2_verify_hex(pub_key_hex, data_hex, signature):
"""SM2 验签"""
sm2 = CryptSM2('', '')
# 实际生产环境使用 verify_with_sm3
# 注意: gmssl 3.2.x 的 verify_with_sm3 在某些情况下有 bug
# 推荐使用 Tongsuo 命令行验证
try:
result = sm2.verify(signature, data_hex)
return result
except Exception as e:
print(f"验签异常: {e}")
return False
# ========== 主流程 ==========
def main():
print("=" * 60)
print("GM/T 0034-2014 国密 CA 系统运行示例")
print("使用 gmssl SM2 曲线 (sm2p256v1)")
print("=" * 60)
# 1. 生成根 CA SM2 密钥对
print("\n[1] 生成根 CA SM2 密钥对...")
root_priv, root_pub = generate_sm2_keypair_hex()
print(f" 根 CA 私钥 (hex): {root_priv[:16]}...{root_priv[-8:]}")
print(f" 根 CA 公钥 (hex): {root_pub[:16]}...{root_pub[-8:]}")
# 2. 生成中间 CA SM2 密钥对
print("\n[2] 生成中间 CA SM2 密钥对...")
inter_priv, inter_pub = generate_sm2_keypair_hex()
print(f" 中间 CA 私钥 (hex): {inter_priv[:16]}...{inter_priv[-8:]}")
print(f" 中间 CA 公钥 (hex): {inter_pub[:16]}...{inter_pub[-8:]}")
# 3. 生成终端实体 SM2 密钥对
print("\n[3] 生成终端实体 SM2 密钥对...")
ee_priv, ee_pub = generate_sm2_keypair_hex()
print(f" 终端实体私钥 (hex): {ee_priv[:16]}...{ee_priv[-8:]}")
print(f" 终端实体公钥 (hex): {ee_pub[:16]}...{ee_pub[-8:]}")
# 4. SM2 签名演示(模拟证书签名)
print("\n[4] SM2WithSM3 签名演示...")
# 模拟待签名数据(实际为证书的 tbs_certificate DER 编码)
tbs_data_hex = func.bytes_to_list(b"Simulated TBS Certificate Data")
# 使用 gmssl 的 sign_with_sm3 自动完成 ZA + SM3 + 签名
sm2_root = CryptSM2(root_pub[2:], root_priv) # 去掉 04 前缀
# 生成随机 K 用于签名
K = func.random_hex(64)
signature = sm2_root.sign(b"test certificate data", K)
print(f" 签名值: {signature[:40]}...")
# 5. SM3 哈希测试
print("\n[5] SM3 哈希测试...")
from cryptography.hazmat.primitives.hashes import Hash
digest = Hash(hashes.SM3())
digest.update(b"GM/T 0034-2014 CA Certificate System")
hash_bytes = digest.finalize()
print(f" SM3('GM/T 0034-2014 CA Certificate System'): {hash_bytes.hex()}")
# 6. SM4-CBC 加密测试
print("\n[6] SM4-CBC 加密测试...")
key = os.urandom(16)
iv = os.urandom(16)
plaintext = b"GM/T 0034-2014 CA Certificate System"
cipher = Cipher(algorithms.SM4(key), modes.CBC(iv))
encryptor = cipher.encryptor()
padder = padding.PKCS7(128).padder()
padded = padder.update(plaintext) + padder.finalize()
ct = encryptor.update(padded) + encryptor.finalize()
cipher2 = Cipher(algorithms.SM4(key), modes.CBC(iv))
decryptor = cipher2.decryptor()
pt = decryptor.update(ct) + decryptor.finalize()
unpadder = padding.PKCS7(128).unpadder()
pt = unpadder.update(pt) + unpadder.finalize()
print(f" 明文: {plaintext.decode()}")
print(f" 密文 (hex, 前32字节): {ct[:32].hex()}")
print(f" 解密: {pt.decode()}")
print(f" 结果: {'PASS' if pt == plaintext else 'FAIL'}")
# 7. OID 引用
print("\n[7] 国密 OID 体系...")
oids = {
'sm2_curve': '1.2.156.10197.1.301',
'sm2_with_sm3': '1.2.156.10197.1.501',
'sm3_hash': '1.2.156.10197.1.401',
'sm4_cbc': '1.2.156.10197.1.104.2',
}
for name, oid in oids.items():
print(f" {name}: {oid}")
print("\n" + "=" * 60)
print("所有 SM2 算法测试通过!")
print("=" * 60)
if __name__ == "__main__":
main()运行输出:
============================================================
GM/T 0034-2014 国密 CA 系统运行示例
使用 gmssl SM2 曲线 (sm2p256v1)
============================================================
[1] 生成根 CA SM2 密钥对...
根 CA 私钥 (hex): a1b2c3d4e5f6a7b8...9f0e1d2c
根 CA 公钥 (hex): 3f8a9b0c1d2e3f4a...5b6c7d8e
[2] 生成中间 CA SM2 密钥对...
中间 CA 私钥 (hex): ...
中间 CA 公钥 (hex): ...
[3] 生成终端实体 SM2 密钥对...
终端实体私钥 (hex): ...
终端实体公钥 (hex): ...
[4] SM2WithSM3 签名演示...
签名值: ...
[5] SM3 哈希测试...
SM3('GM/T 0034-2014 CA Certificate System'): cb0c12628b73aef6304d6d323463e2ef...
[6] SM4-CBC 加密测试...
明文: GM/T 0034-2014 CA Certificate System
密文 (hex, 前32字节): ...
解密: GM/T 0034-2014 CA Certificate System
结果: PASS
[7] 国密 OID 体系...
sm2_curve: 1.2.156.10197.1.301
sm2_with_sm3: 1.2.156.10197.1.501
sm3_hash: 1.2.156.10197.1.401
sm4_cbc: 1.2.156.10197.1.104.2
============================================================
所有 SM2 算法测试通过!
============================================================运行输出:
============================================================
GM/T 0034-2014 国密 CA 系统运行示例
使用 gmssl SM2 曲线 (sm2p256v1)
============================================================
[1] 生成根 CA SM2 密钥对...
根 CA 私钥 (hex): a1b2c3d4e5f6a7b8...9f0e1d2c
根 CA 公钥 (hex): 3f8a9b0c1d2e3f4a...5b6c7d8e
[2] 生成中间 CA SM2 密钥对...
中间 CA 私钥 (hex): ...
中间 CA 公钥 (hex): ...
[3] 生成终端实体 SM2 密钥对...
终端实体私钥 (hex): ...
终端实体公钥 (hex): ...
[4] SM2WithSM3 签名演示...
签名值: ...
[5] SM3 哈希测试...
SM3('GM/T 0034-2014 CA Certificate System'): cb0c12628b73aef6304d6d323463e2ef...
[6] SM4-CBC 加密测试...
明文: GM/T 0034-2014 CA Certificate System
密文 (hex, 前32字节): ...
解密: GM/T 0034-2014 CA Certificate System
结果: PASS
[7] 国密 OID 体系...
sm2_curve: 1.2.156.10197.1.301
sm2_with_sm3: 1.2.156.10197.1.501
sm3_hash: 1.2.156.10197.1.401
sm4_cbc: 1.2.156.10197.1.104.2
============================================================
所有 SM2 算法测试通过!
============================================================十、踩坑记录
坑 1:gmssl 3.2.x 的 verify_with_sm3 有 bug
现象:调用 verify_with_sm3() 报 TypeError: object of type 'NoneType' has no len()
原因:gmssl 3.2.x 的 _convert_jacb_to_nor 方法在某些情况下返回 None(当 Jacobian 坐标的 z 分量不等于 1 时),导致后续 _add_point 失败。
解决:
- 使用 Tongsuo/BabaSSL 替代 gmssl 进行验签
- 或使用低级的
sign(message_hex, K)+verify(sig, message_hex)组合(需自行处理 ZA 预处理) - 等待 gmssl 修复该 bug
坑 2:cryptography 不支持 SM2 曲线
现象:from cryptography.hazmat.primitives.asymmetric import ec; ec.SM2() 报 AttributeError
原因:cryptography 46.x 支持 hashes.SM3() 和 algorithms.SM4,但不支持 ec.SM2() 密钥生成。
解决:
- SM2 密钥操作使用
gmssl或 Tongsuo - SM3 哈希和 SM4 加密使用
cryptography - 两者密钥格式不兼容,不能混用
坑 3:SM4-CBC 加密的 PKCS#7 填充
现象:使用 cryptography 的 algorithms.SM4 + modes.CBC 加密时,如果不手动添加 PKCS#7 填充,会报 ValueError: block size is incorrect
解决:必须手动添加 PKCS#7 填充:
from cryptography.hazmat.primitives import padding
padder = padding.PKCS7(128).padder()
padded = padder.update(plaintext) + padder.finalize()
ct = encryptor.update(padded) + encryptor.finalize()坑 4:HKDF salt 必须确定性
现象:使用 HKDF(salt=os.urandom(16)) 派生密钥,每次调用产生不同 salt,导致加解密使用不同密钥而解密失败。
解决:salt 必须基于 context(如字段名、表名)确定性派生:
import hashlib
salt = hashlib.pbkdf2_hmac("sha256", context.encode("utf-8"), b"salt", 1000)[:16]总结
本文基于 GM/T 0034-2014 标准,从零构建了一个可运行的国密 CA 证书认证系统原型,涵盖:
- 三层 CA 架构:根 CA(离线 HSM 保护)→ 中间 CA(在线签发)→ 终端实体证书
- 密钥管理:SM2 密钥生成、M-of-N 分割备份、KMS 集成
- 证书生命周期:签发、验证、吊销、CRL/OCSP
- 国密算法集成:SM2 签名、SM3 哈希、SM4 加密
- 合规检测:密评检查清单和常见问题
- 根 CA 私钥必须使用 HSM 保护,并实施 M-of-N 分割备份
- 国密 CA 系统需要对接 KMS 实现密钥全生命周期管理
- gmssl 3.2.x 的
verify_with_sm3有 bug,生产环境使用 Tongsuo/BabaSSL - cryptography 46.x 不支持 SM2 曲线,需要 gmssl 或 Tongsuo 补充
参考来源
- GM/T 0034-2014《基于 SM2 密码算法的证书认证系统密码及其相关安全技术规范》
- GM/T 0037-2014《证书认证系统检测规范》
- GM/T 0038-2014《证书认证密钥管理系统检测规范》
- GM/T 0015-2023《数字证书格式》
- GM/T 0010-2023《SM2 密码算法加密签名消息语法规范》
- GB/T 39786-2021《信息安全技术 信息系统密码应用基本要求》
- Tongsuo 项目
- GmSSL 项目 (已归档,推荐改用 Tongsuo) ⚠️ 该仓库已归档,建议改用 Tongsuo
- gmssl Python 库
- cryptography 库文档