国密SM2证书全生命周期管理实战:从签发到吊销的完整工程方案
前言
国密改造进入深水区后,很多企业发现:证书管理比算法选型更复杂。Nginx配好国密密码套件只是第一步,后续还有密钥生成、CSR制作、CA签发、证书分发、吊销更新、到期续期等一系列工程问题。
标准如GM/T 0034-2014《基于SM2密码算法的证书认证系统密码及其相关安全技术规范》给出了框架要求,但落地时需要解决的具体问题包括:
- SM2密钥对如何生成?椭圆曲线参数怎么选?
- CSR(证书签名请求)的格式和内容有什么要求?
- CA如何签发符合GM/T 0015-2023规范的SM2证书?
- 证书吊销时,CRL和OCSP如何同步更新?
- 证书快到期了,自动化续期的流程怎么设计?
一、SM2密钥对生成
1.1 使用gmssl生成密钥对
GM/T 0003.1-2012定义了SM2椭圆曲线参数,使用gmssl库可以快速生成密钥对:
# gm_sm2_keygen.py
from gmssl import func, sm2
# SM2曲线参数(GM/T 0003.1-2012 附录A)
# p = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
# a = FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
# b = 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
# Gx = 32C4AE2C 1F998463 DD9528B2 B963ABD9 53FE0D80 1AFE08D0 3D4947CA 0EF9E965
# Gy = BC3736A2 F4F6779C B936E423 E4D2C0EE 6889D174 0C962AC2 7A870324 6DDA3096
# 使用默认椭圆曲线表(gmssl内置)
sm2_crypt = sm2.CryptSM2(
public_key='0432C4AE2C1F998463DD9528B2B963ABD953FE0D801AFE08D03D4947CA0EF9E965BC3736A2F4F6779CB936E423E4D2C0EE6889D1740C962AC27A8703246DDA3096',
private_key='', # 空表示生成新密钥
sec_level=128
)
# 生成密钥对
private_key, public_key = sm2_crypt.generate_key()
print(f"私钥: {private_key}")
print(f"公钥: {public_key}")运行结果:
私钥: 5E9A8F7C3D2B1A...(64位十六进制)
公钥: 0432C4AE2C1F998463...(130位,含04前缀)1.2 使用cryptography库生成密钥对
对于需要与其他库互操作的项目,可以使用cryptography库:
# cryptography_sm2_keygen.py
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization
from gmssl import func
# 注意:cryptography标准库不直接支持SM2曲线
# 需要使用gmssl或Tongsuo的扩展
# 以下为替代方案:使用SM2兼容的P-256曲线(仅用于演示,非国密合规)
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# 序列化为PEM格式
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(f"私钥PEM:\n{private_pem.decode()}")
print(f"公钥PEM:\n{public_pem.decode()}")⚠️ 重要提示:上述代码使用SECP256R1(P-256)曲线,这是国际标准曲线,不是国密SM2曲线。SM2曲线参数由GM/T 0003.1-2012定义,使用不同的素数和曲线方程。生产环境必须使用支持SM2的库(如gmssl、Tongsuo)。
1.3 密钥存储安全要求
GM/T 0034-2014第5.3节要求CA系统对私钥进行安全保护:
| 密钥类型 | 存储方式 | 安全要求 |
|---|---|---|
| 根CA私钥 | HSM离线存储 | 物理隔离,多人控制 |
| 中间CA私钥 | HSM在线存储 | 访问审计,密钥分割 |
| 终端实体私钥 | 本地安全存储 | 加密存储,权限控制 |
- 根CA/中间CA:江南计算JLSM系列、三未信安SJK1926等HSM设备
- 终端实体:操作系统密钥链(macOS Keychain、Linux Secret Service)或加密文件
二、CSR生成与提交
2.1 SM2 CSR结构
CSR(Certificate Signing Request)包含申请者信息和公钥,使用PKCS#10格式。GM/T 0010-2023规定了SM2证书的CSR格式要求:
# generate_sm2_csr.py
from gmssl import sm2, func
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
import datetime
# 生成SM2密钥对
sm2_crypt = sm2.CryptSM2(
public_key='',
private_key='',
sec_level=128
)
private_key_hex, public_key_hex = sm2_crypt.generate_key()
# 构造X.509 Subject
subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Beijing"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "示例公司"),
x509.NameAttribute(NameOID.OrganizationalUnitName, "信息技术部"),
x509.NameAttribute(NameOID.COMMON_NAME, "example.com"),
])
# 构造CSR
csr = x509.CertificateSigningRequestBuilder().subject_name(subject).sign(
# 注意:cryptography库不直接支持SM2签名
# 这里使用占位符,实际应使用gmssl或Tongsuo
private_key=None, # 需替换为SM2密钥
algorithm=hashes.SM3() # SM3哈希
)
# 导出CSR(PEM格式)
csr_pem = csr.public_bytes(serialization.Encoding.PEM)
print(csr_pem.decode())2.2 CSR字段要求
根据GM/T 0010-2023,SM2 CSR必须包含以下字段:
| 字段 | 必填 | 说明 |
|---|---|---|
| subject | 是 | 申请者信息,包含CN、O、C等 |
| public_key | 是 | SM2公钥,使用SM2曲线参数 |
| signature_algorithm | 是 | SM2WithSM3(OID: 1.2.156.10197.1.501) |
| attributes | 否 | 可选,如challengePassword |
- 使用SHA-256代替SM3哈希:不符合国密要求,无法通过密评
- 使用SECP256R1代替SM2曲线:密钥不兼容,无法与国密系统交互
三、CA签发SM2证书
3.1 签发流程
GM/T 0034-2014第6章规定了CA签发流程:
1. 验证CSR签名(使用CSR中的公钥)
2. 验证申请者身份(线下或线上审核)
3. 生成证书序列号(唯一、单调递增)
4. 设置证书有效期(不超过1年)
5. 使用CA私钥签名证书
6. 输出符合GM/T 0015-2023的X.509证书3.2 Python签发实现
# ca_sign_sm2_certificate.py
from gmssl import sm2, func
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtensionOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
import datetime
def sign_sm2_certificate(ca_private_key, ca_cert, csr, validity_days=365):
"""
使用CA签发SM2证书
参数:
ca_private_key: CA的SM2私钥(十六进制)
ca_cert: CA的X.509证书
csr: 申请者CSR
validity_days: 证书有效期(天)
返回:
签发的X.509证书
"""
# 生成证书
cert = (
x509.CertificateBuilder()
.subject_name(csr.subject)
.issuer_name(ca_cert.subject)
.public_key(csr.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.utcnow())
.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=validity_days))
.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
)
.add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
),
critical=True,
)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(csr.public_key()),
critical=False,
)
)
# 使用CA私钥签名(使用SM2WithSM3算法)
# 注意:cryptography标准库不支持SM2签名
# 实际应使用gmssl或Tongsuo的CA模块
signed_cert = cert.sign(ca_private_key, hashes.SM3())
return signed_cert
# 使用示例
if __name__ == '__main__':
# 加载CA密钥和证书
ca_private_key = open('ca_private_key.pem').read()
ca_cert = x509.load_pem_x509_certificate(open('ca_cert.pem').read())
csr = x509.load_pem_x509_csr(open('csr.pem').read())
# 签发证书
cert = sign_sm2_certificate(ca_private_key, ca_cert, csr, validity_days=365)
# 保存证书
with open('issued_cert.pem', 'wb') as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
print("证书签发成功")3.3 证书扩展字段要求
GM/T 0015-2023规定了SM2证书必须包含的扩展:
| 扩展 | 是否必需 | OID |
|---|---|---|
| BasicConstraints | 是 | 2.5.29.19 |
| SubjectKeyIdentifier | 是 | 2.5.29.14 |
| AuthorityKeyIdentifier | 是 | 2.5.29.35 |
| KeyUsage | 是 | 2.5.29.15 |
| SubjectAltName | 推荐 | 2.5.29.17 |
| CertificatePolicies | 推荐 | 2.5.29.32 |
- SM2签名算法OID:1.2.156.10197.1.501(在AlgorithmIdentifier中)
- SM2曲线参数OID:1.2.156.10197.1.301(在SubjectPublicKeyInfo中)
四、证书吊销机制
4.1 CRL(证书吊销列表)
GM/T 0037-2014《证书认证系统检测规范》要求CA系统支持CRL吊销机制:
# revoke_certificate_crl.py
from cryptography import x509
from cryptography.x509 import ocsp
from cryptography.hazmat.primitives import hashes, serialization
import datetime
def create_crl(ca_cert, ca_private_key, revoked_certs):
"""
创建CRL(证书吊销列表)
参数:
ca_cert: CA证书
ca_private_key: CA私钥
revoked_certs: 被吊销证书列表,每个元素为(dict)
{
'serial_number': int,
'revocation_time': datetime,
'reason': x509.ReasonFlags
}
"""
builder = x509.CertificateRevocationListBuilder()
builder = builder.issuer_certificate(ca_cert)
builder = builder.last_update(datetime.datetime.utcnow())
builder = builder.next_update(datetime.datetime.utcnow() + datetime.timedelta(days=7))
for rev in revoked_certs:
builder = builder.add_revoked_certificate(
x509.RevokedCertificateBuilder()
.certificate_serial_number(rev['serial_number'])
.revocation_date(rev['revocation_time'])
.revocation_reason(rev.get('reason', x509.ReasonFlags.unspecified))
.build()
)
crl = builder.sign(ca_private_key, hashes.SHA256())
return crl
# 吊销证书示例
revoked_certs = [
{
'serial_number': 123456,
'revocation_time': datetime.datetime.utcnow(),
'reason': x509.ReasonFlags.key_compromise,
}
]
crl = create_crl(ca_cert, ca_private_key, revoked_certs)
# 保存CRL
with open('crl.pem', 'wb') as f:
f.write(crl.public_bytes(serialization.Encoding.PEM))4.2 OCSP(在线证书状态协议)
OCSP提供实时证书状态查询,比CRL更高效:
# ocsp_response.py
from cryptography.x509 import ocsp
from cryptography.hazmat.primitives import hashes, serialization
import datetime
def create_ocsp_response(ca_cert, issuer_cert, serial_number, status):
"""
创建OCSP响应
参数:
ca_cert: 被查询证书的CA证书
issuer_cert: 签发CA的证书
serial_number: 证书序列号
status: ocsp.OCSPCertStatus(good/revoked/unknown)
"""
builder = ocsp.OCSPResponseBuilder()
builder = builder.add_response(
cert=ca_cert,
issuer=issuer_cert,
algorithm=hashes.SHA256(),
serial_number=serial_number,
cert_status=status,
this_update=datetime.datetime.utcnow(),
next_update=datetime.datetime.utcnow() + datetime.timedelta(hours=24),
)
# 签名OCSP响应
response = builder.sign(ca_private_key, hashes.SHA256())
return response4.3 吊销原因码
GM/T 0037-2014定义了以下吊销原因:
| 原因码 | 说明 |
|---|---|
| unspecified | 未指定原因 |
| key_compromise | 密钥泄露 |
| ca_compromise | CA密钥泄露 |
| affiliation_changed | 所属关系变更 |
| superseded | 证书更新替代 |
| cessation_of_operation | 业务停止 |
| certificate_hold | 证书暂停 |
五、自动化续期流程
5.1 续期策略
GM/T 0034-2014建议证书有效期不超过1年,续期策略如下:
| 有效期 | 续期触发时间 | 处理方式 |
|---|---|---|
| ≤30天 | 到期前30天 | 自动续期 |
| 30-90天 | 到期前60天 | 提醒管理员 |
| >90天 | 到期前90天 | 计划续期 |
5.2 Python自动化脚本
# auto_renew_certificates.py
import sqlite3
import datetime
from cryptography import x509
from cryptography.x509.oid import NameOID
def check_certificate_expiry(db_path, threshold_days=30):
"""
检查数据库中即将过期的证书
参数:
db_path: 数据库路径
threshold_days: 预警阈值(天)
返回:
即将过期的证书列表
"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 查询证书信息
cursor.execute('''
SELECT id, serial_number, common_name, not_after
FROM certificates
WHERE is_active = 1
ORDER BY not_after ASC
''')
expiring_certs = []
now = datetime.datetime.utcnow()
threshold = now + datetime.timedelta(days=threshold_days)
for row in cursor.fetchall():
cert_id, serial, cn, not_after = row
if not_after < threshold:
expiring_certs.append({
'id': cert_id,
'serial': serial,
'cn': cn,
'expires': not_after,
'days_left': (not_after - now).days
})
conn.close()
return expiring_certs
def renew_certificate(cert_info, ca_private_key, ca_cert):
"""
续期证书
参数:
cert_info: 证书信息字典
ca_private_key: CA私钥
ca_cert: CA证书
返回:
新证书
"""
# 生成新的CSR(使用原证书的主题)
new_csr = generate_csr(cert_info['cn'])
# 签发新证书(有效期从当前时间开始)
new_cert = sign_sm2_certificate(
ca_private_key,
ca_cert,
new_csr,
validity_days=365
)
# 吊销旧证书
revoke_certificate(cert_info['serial'])
# 更新数据库
update_certificate_in_db(cert_info['id'], new_cert)
return new_cert
# 定时任务示例(使用cron)
# 每天凌晨2点执行续期检查
# 0 2 * * * /usr/bin/python3 /path/to/auto_renew_certificates.py5.3 与ACME协议集成
对于自动化程度较高的场景,可以集成ACME协议:
# acme_integration.py
import acme
from acme import client, messages, errors
from cryptography import x509
# 配置ACME服务器(如Let's Encrypt或内部ACME服务)
account_key = acme.crypto_utility.generate_rsa_key(2048)
client = acme.client.Client(
directory=messages.Directory.load('https://acme.example.com/directory'),
net=acme.client.Network(account_key)
)
# 注册账户
registration = client.register(acme.messages.NewRegistration.from_data(
fields=['email'],
external_account_binding=None
))
# 授权域名
authz = client.authorize(identifier=messages.Identifier(
typ=messages.IdentifierType.DNS,
value='example.com'
))
# 获取证书
cert = client.get_final_order(authz).get_certificate()
cert_pem = cert.cert.public_bytes(encoding=x509.Encoding.PEM)六、工程实践建议
6.1 密钥安全管理
- 密钥分割:根CA密钥采用M-of-N分割,需要多个管理员同时操作
- 访问审计:所有密钥操作记录日志,定期审计
- 密钥轮换:CA密钥定期轮换,终端实体密钥按需轮换
- 灾备恢复:密钥备份存储在异地,支持灾难恢复
6.2 证书模板设计
根据业务需求设计证书模板:
| 模板类型 | 用途 | 有效期 | 关键扩展 |
|---|---|---|---|
| TLS服务器 | Nginx/Apache国密改造 | 1年 | SubjectAltName(DNS/IP) |
| TLS客户端 | mTLS双向认证 | 1年 | ClientAuth |
| 代码签名 | 软件发布签名 | 2年 | CodeSigning |
| 文档签名 | PDF/Office签名 | 1年 | DocumentSigning |
6.3 合规检查清单
密评现场检查时,证书管理系统需要提供:
- [ ] 密钥生成日志(包含时间、操作人、算法)
- [ ] 证书签发记录(包含序列号、有效期、主题)
- [ ] 证书吊销记录(包含吊销时间、原因)
- [ ] CRL发布记录(包含发布时间、版本号)
- [ ] OCSP响应日志(包含查询时间、证书序列号)
- [ ] 密钥备份恢复测试记录
总结
国密SM2证书全生命周期管理涉及密钥生成、CSR签发、证书吊销、自动化续期等多个环节。本文提供的Python脚本和工程实践建议,可以帮助企业快速构建符合GM/T 0034-2014规范的证书管理系统。
核心要点:
- 使用支持SM2的库(gmssl、Tongsuo)生成密钥和签名
- 确保证书格式符合GM/T 0015-2023规范
- 建立完善的吊销机制(CRL + OCSP)
- 实现自动化续期,避免证书过期影响业务
- 结合实际业务系统,定制证书模板
- 集成到现有的ITSM/运维平台
- 定期进行密钥轮换和证书审计
参考标准:
- GM/T 0003.1-2012《SM2椭圆曲线公钥密码算法 第1部分:总则》
- GM/T 0009-2023《SM2密码算法使用规范》
- GM/T 0010-2023《SM2密码算法加密签名消息语法规范》
- GM/T 0015-2023《数字证书格式》
- GM/T 0034-2014《基于SM2密码算法的证书认证系统密码及其相关安全技术规范》
- GM/T 0037-2014《证书认证系统检测规范》