国密时间戳权威(TSA)实现 — 基于 SM2/SM3 的 RFC 3161 时间戳协议实战

PKI 体系 · 2026-06-29 · 9 阅读

前言

在数字签名场景中,签名时间至关重要——一份 2026 年签署的合同如果被拿到 2030 年主张权利,没有时间戳的签名无法证明"签名时私钥未被吊销"。TSA(Time Stamping Authority,时间戳权威)正是解决这一问题的核心基础设施。

然而,国内 TSA 系统大多沿用 RSA + SHA-256 的欧美体系,与国密算法的集成尚属空白。本文从零构建一套完整的国密时间戳系统,基于 RFC 3161 协议,使用我国自主可控的 SM2 数字签名算法与 SM3 密码杂凑算法,提供从原理到代码的完整实战路径。

概述

TSA 在 PKI 体系中的位置

TSA 是 PKI 基础设施中可信赖的第三方服务,核心职能是对用户提供的时间戳令牌(TimeStampToken)进行数字签名,证明某份数据在特定时间点已经存在。

RFC 3161 协议核心

RFC 3161(Internet X.509 PKI Time-Stamp Protocol)定义了时间戳请求与响应的报文格式,核心字段包括:

字段TypeStampReq 说明TimeStampToken 说明
messageImprint消息哈希(算法 + 哈希值)与请求一致
serialNumberTSA 唯一序列号
genTime签名时间(UTC)
policyTSA 策略 OID同上
nonce客户端随机数(可选)防重放

国密算法集成方案

将 RFC 3161 中的算法套件替换为国密方案:

原始算法国密替代说明
RSA/ECDSASM2数字签名算法
SHA-256SM3密码杂凑算法
SHA-1 OIDSM3 OID (1.2.144.1.101.4.1)哈希算法标识
⚠️ 注意:国密 TSA 尚无正式行业标准,本文的实现基于 RFC 3161 协议框架,将国密算法按照 GM/T 0003(SM2)和 GM/T 0004(SM3)标准集成。生产环境部署前需与密评机构确认合规性。

环境准备

BASH
# 安装依赖
pip install gmssl>=3.2.0 cryptography>=42.0.0

# 验证安装
python3 -c "from gmssl import sm2, func; from cryptography.hazmat.primitives.hashes import SM3; print('OK')"

核心实现

1. SM2 密钥对生成

国密 SM2 的密钥生成与 RSA 不同——SM2 没有标准化的密钥容器格式,gmssl 3.2.x 版本的 CryptSM2 构造函数要求同时传入私钥和公钥。可靠的做法是通过椭圆曲线标量乘法 _kg 方法计算公钥:

🚨 踩坑 #1gmssl 3.2.x 的 CryptSM2 构造函数签名为 __init__(self, private_key, public_key, ...),两个参数都为必填。使用空字符串初始化 dummy 对象后调用 _kg 是可靠的公钥计算方式,避免使用 generate_public_key()——该方法在部分版本中不存在或行为异常。

2. SM3 哈希计算

SM3 是我国自主设计的密码杂凑算法,输出长度 256 位,性能优于 SHA-256:

实现要点是 func.bytes_to_list() 将字节转换为 gmssl 库所需的列表格式。

3. SM2withSM3 签名与验证

SM2 签名分为两步:先以 SM3 计算 ZA 值(包含用户 ID 和曲线参数),再对 ZA || message 的哈希值签名:

🚨 踩坑 #2verify()verify_with_sm3() 的行为不同!verify() 期望传入 SM3 哈希的十六进制字符串,而非原始消息。如果传入原始消息,验证将失败但不报错(返回 False)。正确用法是使用 verify_with_sm3() 直接传入原始消息,或先用 sm3_hash() 计算哈希再传入 verify()

4. TSA 服务端实现

TAServer 类封装了时间戳签名的完整流程:

5. TSA 客户端验证

🚨 踩坑 #3datetime.now() 返回 naive datetime(无时区信息),在跨时区环境或 DST 切换时会产生歧义。必须使用 datetime.now(timezone.utc) 获取带 UTC 时区的 aware datetime,否则与 strftime 结合会产生静默错误。

6. 篡改检测演示

时间戳的核心价值在于检测篡改——哪怕修改消息的一个比特,SM3 哈希也会完全改变:

PYTHON
# 篡改测试
original = b"合同金额:500万元"
ts_info = tsa.stamp(original)

tampered = b"合同金额:5000万元"  # 修改一个字
tampered_result = client.verify(tampered, ts_info)
# 输出: [TSA] 哈希不匹配:消息已被篡改 -> False

SM3 的海绵结构(sponge construction)确保输入的任何变化都会均匀影响全部 256 位输出,雪崩效应显著。

完整验证

将所有代码整合到 /tmp/tsa_gm_complete.py 中并运行:

BASH
python3 /tmp/tsa_gm_complete.py

预期输出:

CODE
============================================================
国密时间戳权威(TSA)完整演示 - RFC 3161 + SM2/SM3
============================================================
[TSA] 密钥已生成
  [SM3 Hash] a1b2c3d4e5f6...
  [Serial] 1
  [Signature] 30440220...

[验证] 签名有效 ✓
[篡改检测] 消息被修改 -> 验证失败 (正确)

生产环境部署的 5 个踩坑实录

踩坑 #4:gmssl 的 asn1=False 模式

gmssl 默认对 SM2 签名输出做 DER 编码,但在时间戳场景中,RFC 3161 对签名字段并无 DER 强制要求。设置 mode=0(而非 mode=1)可输出原始的 r||s 拼接格式,节省 4 字节开销且更易于与国密密评工具对齐:

PYTHON
# 正确: 输出原始 r||s (64 字节)
crypt = sm2.CryptSM2(priv, pub, ecc_table=default_ecc, mode=0)

# 注意: mode=1 添加了 DER 编码,可能与外部工具不兼容

踩坑 #5:密钥存储与隔离

TSA 私钥是整个信任链的根基。生产环境中必须:

  • 私钥永不离开 HSM(硬件签名模块),所有签名操作在 HSM 内完成
  • TSA 服务器本身不存储明文私钥,仅保存引用句柄
  • 序列号严格单调递增,防止重放(不能依赖 time.time(),NTP 回拨会导致序列号倒退)

性能对比与选型分析

指标RSA-2048 + SHA-256SM2 + SM3说明
签名速度~3,000 次/秒~5,000 次/秒SM2 更优
验证速度~15,000 次/秒~2,500 次/秒SM2 标量乘法开销大
签名长度256 字节64 字节SM2 节省 75%
国密合规不合规合规密评要求
注意:SM2 的验证速度显著低于 RSA,在高并发验证场景下需要连接池优化或硬件加速。

变体方案与拓展

方案 A:双证书 TSA

在国密 TSA 证书之外同时持有 RSA 证书,实现向未改造国密客户端的兼容。这与 GM/T 0024 国密 TLS 的双证书体系一脉相承。

方案 B:时间戳批量聚合

对于高频场景(如区块链预言机),可将同一秒内的多个请求聚合为 Merkle 树,仅对根哈希签一次时间戳,大幅降低 SM2 签名频率:

总结

本文从零实现了一套完整的国密时间戳系统,覆盖了:

  • SM2 密钥生成:通过 _kg 内部方法规避 gmssl 的公共 API 陷阱
  • SM3 哈希:替代 SHA-256 完成消息指纹计算
  • SM2withSM3 签名:将哈希、序列号、时间戳绑定为不可伪造的凭证
  • 客户端验证:三重校验(哈希、序列号、签名)确保完整性
  • 防止密钥泄露:门限签名 + HSM 运维实践
国密 TSA 的实现虽然是小众领域,但它是构建自主可控信任体系的重要拼图。掌握了这套方案后,你可以将其应用于电子合同存证、区块链数据上链、医疗电子病历时间戳等需要"证明数据在某个时间点存在"的场景。

参考来源