技术

理解PKI(七):证书如何在应用中工作——TLS、数字信封与可信时间

沿一次电子文件签署流程,说明 TLS 1.3 如何认证连接、CMS 数字签名与数字信封如何保护消息,以及 RFC 3161 时间戳如何补充可信时间和长期验证证据。

发布
阅读约 13 分钟
理解PKI(七):证书如何在应用中工作——TLS、数字信封与可信时间
章节索引页面导航1 / 30
一、同一张证书,进入应用后承担不同角色1 / 30

用户通过 HTTPS 打开电子合同,登录系统,确认内容,使用私钥签名,平台再给签名加上时间戳并把合同加密归档。整个过程都在使用 PKI,但每一步解决的问题并不相同。

TLS 证明当前连接到了哪个服务,并保护传输中的字节;用户签名把身份和一份确定文件绑定;数字信封控制谁能读取文件;时间戳证明某个摘要在特定时间之前已经存在;业务系统则记录用户看到了什么、点击了什么以及当时拥有什么权限。

如果把它们都压缩成“用了证书,所以安全”,就会产生几个危险误解:HTTPS 成功不等于用户签过合同,签名中的本机时间不等于可信时间,文件经过公钥加密也不等于知道发送者是谁。

这篇文章不再重复第六篇的路径、状态和用途验证,而是沿一条端到端业务流程回答:

证书怎样进入协议,私钥究竟签了什么,业务数据由什么密钥加密,时间由谁证明,最终又要保存哪些证据。


一、同一张证书,进入应用后承担不同角色

PKI 应用不是把一种密码操作复制到所有场景,而是组合不同保护边界:

应用对象需要证明什么主要机制证据能否脱离连接保存
TLS 连接当前对端控制服务器或客户端证书私钥CertificateVerify、握手 transcript、Finished通常不能直接充当业务文件签名
电子文件签名者私钥对确定内容产生了签名CMS SignedData、PDF/XML 签名等可以随文件长期保存
保密消息只有指定接收者能取得内容密钥CMS EnvelopedData、混合加密可以作为加密对象传输和归档
可信时间某个摘要在 TSA 给出的时间前已经存在RFC 3161 TimeStampToken可以与签名或文件共同保存
业务授权当前身份是否有权执行这次动作账号、角色、交易规则、用户确认与审计需要业务证据独立留存

证书在这些场景中提供公钥、身份和用途声明,私钥控制则由具体协议证明。应用还必须决定签名对象、上下文、时效和授权,不能期待证书替自己完成全部语义。


二、TLS 1.3:证书认证连接,不负责签合同

现代 TLS 1.3 握手可以简化为三组动作:协商参数、建立共享秘密、认证握手。

客户端                                           服务器
  │ ClientHello + key_share                         │
  ├────────────────────────────────────────────────►│
  │                         ServerHello + key_share │
  │◄────────────────────────────────────────────────┤
  │       EncryptedExtensions                       │
  │       Certificate                               │
  │       CertificateVerify                         │
  │       Finished                                  │
  │◄────────────────────────────────────────────────┤
  │ Certificate(仅客户端认证时)                    │
  │ CertificateVerify(仅客户端认证时)              │
  │ Finished                                        │
  ├────────────────────────────────────────────────►│
  │◄══════════ AEAD 保护的 Application Data ═══════►│
text

证书没有直接加密网页内容

在常见的 TLS 1.3 证书认证握手中,双方通过 (EC)DHE 等密钥交换建立共享秘密,再经 HKDF 派生握手和应用流量密钥。真正保护 HTTP 数据的是对称 AEAD 算法,而不是服务器证书中的 RSA、ECDSA 或其他公钥直接加密每个数据包。

证书公钥主要进入身份认证:服务器发送证书链,随后用对应私钥生成 CertificateVerify,对包含上下文字符串和握手 transcript 摘要的结构签名。客户端验签成功,才得到服务器当前确实控制证书私钥的证明。

Finished 则对整个握手 transcript 计算 MAC,完成密钥确认并把协商参数、身份认证与本次会话密钥绑定。证书验证、CertificateVerifyFinished 各自承担不同职责,缺一不可。

单向 TLS 与双向 TLS

普通 HTTPS 通常只认证服务器。用户登录仍依靠口令、Passkey、OTP 或应用自己的认证流程。双向 TLS(mTLS)会由服务器发出 CertificateRequest,客户端再发送证书和 CertificateVerify,证明自己控制客户端私钥。

mTLS 得到的是一个经过认证的客户端证书身份。应用仍要把它映射到账户、租户和权限;服务器请求客户端证书也不代表用户已经确认某份合同。

TLS 握手为什么不能代替文件签名

TLS 保护的是一段会话。业务请求离开连接、进入消息队列或数据库后,接收者通常不能仅凭文件本身重新验证“哪个用户签署了哪一版内容”。服务端也是 TLS 端点,它能看到连接中的明文,并可以生成自己的业务记录。

TLS 证明数据经由受保护连接传输;文件签名证明某个私钥对确定内容产生了可独立验证的签名。两种证据不能互相替代。


三、挑战应答:把私钥控制绑定到本次会话

证书登录或高风险操作经常使用挑战应答。服务器生成一次性、不可预测的 challenge,客户端把 challenge、服务域、操作类型、会话标识和必要的业务摘要组成待签上下文,再调用私钥签名。

服务器:challenge + session_id + action + document_hash


客户端:展示关键内容 → 用户确认 → 私钥签名


服务器:验签 + 验证证书 + 检查 challenge 未使用且未过期
text

challenge 的价值是新鲜度。若每次请求都签同一个固定字符串,攻击者可以重放旧签名。上下文绑定则防止一份“登录签名”被改作“确认付款”,或把 A 合同的确认挪到 B 合同。

但挑战应答本身仍不自动形成通用文档签名。若争议处理需要任何验证方脱离原系统检查文件,就应生成 CMS、PDF、XML 或业务规范规定的独立签名对象,并保存其验证材料。


四、CMS SignedData:签名的不是一个裸哈希

第四篇已经介绍过 PKCS#7 与 CMS 的容器边界。进入应用后,CMS SignedData 可以把内容、签名者标识、算法、证书和属性组织成可交换对象。

SignedData
├── digestAlgorithms
├── encapContentInfo ─── 原文或原文引用
├── certificates ─────── 签名证书及候选链材料
└── signerInfos
    ├── signerIdentifier
    ├── digestAlgorithm
    ├── signedAttrs
    │   ├── contentType
    │   └── messageDigest ── 原文摘要
    ├── signatureAlgorithm
    ├── signature
    └── unsignedAttrs ────── 可放签名时间戳等后加属性
text

signedAttrs 存在时,签名前的摘要输入是它的完整 DER 编码,而不是直接使用原文摘要。这里有一个容易写错的编码边界:SignerInfo 中的 signedAttrs 在线上使用 [0] IMPLICIT 标签,但计算签名时必须单独编码为带 SET OF 标签的 DER,不能截取消息里的 [0] 字段原始字节直接验签。messageDigest 属性又把原文摘要纳入已签属性,从而把内容类型、摘要和其他受保护属性一起绑定。这个“线上字段编码”和“签名输入编码”的区别也可以回看第四篇

验证方不能信任消息里自带的摘要。它必须重新计算原文摘要,与 messageDigest 比较,再验证 signedAttrs 的签名,并独立验证签名证书的路径、状态、时间和用途。

内嵌签名与分离签名

CMS 可以把原文放进 encapContentInfo,也可以只保存签名,把原文放在外部。分离签名便于保留原文件格式,但验证时必须提供完全相同的字节。换行转换、字符编码、JSON 字段排序或 PDF 增量更新都可能改变摘要。

因此,业务系统要在签名前确定规范化规则、文件版本和展示内容。不能让客户端看到一段文本,却让密码模块签另一组未经确认的字节。

signingTime 不是可信时间

CMS 的 signingTime 是签名者声称的签署时间。它虽然可以进入 signedAttrs,防止签名后被静默修改,却通常来自签名设备或应用时钟。签名者可以把自己的时钟调错,因此它不能单独证明客观签署时刻。

可信时间需要由独立 TSA 使用受控时钟签发时间戳令牌。


五、数字信封:用公钥保护内容密钥

直接用 RSA、SM2 或其他公钥算法加密整份大文件,既低效又受到明文长度和填充规则限制。数字信封采用混合加密:

  1. 为本次消息生成随机内容加密密钥(CEK)和所需 nonce;
  2. 使用对称算法加密业务内容;
  3. 针对每个接收者,用密钥传输、密钥协商、KEM 或预置 KEK 等机制保护同一 CEK;
  4. 把加密内容、算法参数和每个接收者的 RecipientInfo 装入 CMS 对象。
                            ┌─ 接收者 A 的公钥/密钥机制 ─► encrypted CEK A
随机 CEK ─► AEAD 加密原文 ──┼─ 接收者 B 的公钥/密钥机制 ─► encrypted CEK B
       │                    └─ 接收者 C 的公钥/密钥机制 ─► encrypted CEK C
       └──────────────────────────────► encrypted content
text

每个接收者只需解开自己的 CEK,再对称解密同一份密文。增加接收者不必把大文件重新加密多份。

加密不自动证明发送者

任何取得接收者公钥的人都能给他加密消息,因此数字信封主要提供保密性,不天然证明发送者身份。若同时需要来源和完整性,可以先生成签名对象,再把整个签名对象装入信封;接收者先解密,再验证内部签名。

对于新系统,还要明确使用带认证的内容加密 Profile。CMS 基础规范允许历史上常见的 CBC 等算法;S/MIME 4.0 增加了 AuthEnvelopedData、AES-GCM 和 ChaCha20-Poly1305 等选择。不能把“CMS 信封”自动等同于已经抵抗密文篡改。

签名后加密让签名只对能解密的接收者可见;加密后签名则会暴露签名者,并让签名覆盖密文。两种顺序的隐私、转发和审计语义不同,应由协议明确规定。


六、RFC 3161 时间戳:TSA 签的是摘要和时间声明

RFC 3161 的时间戳协议不会把整份合同发送给 TSA。客户端先计算目标对象的摘要,将算法标识和摘要值组成 messageImprint,再提交时间戳请求。

合同或签名值 ──摘要──► messageImprint
                           │ + policy、nonce、certReq

                          TSA
                           │ 受控时钟 + TSA 私钥

                    TimeStampToken(CMS SignedData)
                           └── TSTInfo
                               ├── policy
                               ├── messageImprint
                               ├── serialNumber
                               ├── genTime / accuracy
                               ├── ordering
                               └── nonce
text

TSA 的声明是:这个 messageImprint 所代表的数据在 genTime 之前已经存在。它没有看到原文时,不能判断摘要对应的是合同、图片还是随机数据;它也不证明谁创建或签署了对象。

如果时间戳目标是签名值,它可以证明该签名不晚于某个时间已经存在,这也是文档签名和代码签名中的常见做法。如果目标是原文摘要,它证明的是数据存在时间,两者不要混为一谈。

验证时间戳不能只读 genTime

验证方至少要检查:

RFC 5816 更新 RFC 3161:使用 SHA-1 之外的证书摘要算法时,应通过 SigningCertificateV2/ESSCertIDv2 标识 TSA 证书。实现不能只照搬 2001 年版本中的 SHA-1 结构。

签名者本机时间是“我说我何时签”,RFC 3161 时间戳是“TSA 证明这个摘要不晚于何时已经存在”。


七、一份电子合同怎样形成完整证据链

把前面的机制放进同一流程,可以看到它们如何衔接而不互相越权。

1. 建立受保护连接

客户端验证服务器证书和 TLS 1.3 握手,确认连接目标并建立 AEAD 流量密钥。TLS 防止传输途中被窃听或篡改,但不直接形成合同签名。

2. 认证用户并完成业务授权

系统通过 mTLS、Passkey、UKey 挑战签名或其他方式认证用户,再检查账户、租户、角色和签署权限。高风险动作把 challenge 与合同 ID、版本和摘要绑定。

3. 固定待签字节和用户意愿

平台展示完整合同及关键条款,记录用户确认过程,并把确定版本转换为规范化待签字节。任何签后修改都会产生新摘要和新版本。

4. 生成可携带的文件签名

客户端私钥生成 CMS、PDF 或业务规范要求的签名对象。服务端重新计算内容摘要、验证签名、证书链、状态和用途,并保存原始签名对象,而不只是保存“验签成功”。

5. 为签名取得可信时间

平台对签名值或签名对象计算摘要,向 TSA 请求 RFC 3161 时间戳。返回令牌经验证后作为未签属性或独立证据与签名关联。

6. 按接收者加密和分发

若合同包含敏感信息,平台使用数字信封为指定归档系统、交易双方或监管接收者保护内容密钥。加密权限和业务查看权限仍要保持一致。

7. 保存长期验证材料

一份可复核档案至少应考虑保存:

多年后重新验证时,当前证书已经过期并不必然否定历史签名。验证方需要判断签名和时间戳是否完整、签署时证书是否可接受、状态证据是否覆盖相应时刻,以及算法是否仍具有足够可信度。必要时还要在旧算法失效前补充新的存档时间戳。


八、S/MIME、代码签名和 IPSec 只是保护对象不同

同样的组合方式会出现在不同应用中:

场景保护对象证书和私钥做什么不能自动证明什么
S/MIMEMIME 正文和附件SignedData 证明来源,信封控制收件人邮件头可能仍暴露,签名不代表内容真实
代码签名可执行文件、安装包或发布制品绑定发布者与制品摘要,安全时间戳支持过期后验证软件没有漏洞、运行时一定安全
IPSec/IKEIP 包或网段间安全关联证书可认证 IKE 对端,派生 SA 密钥上层用户身份和具体业务授权
电子合同规范化文档与签署动作文件签名、TSA 时间、业务审计组合成证据单靠密码结果获得当然法律效力

协议相同,客户端结论仍可能不同

RFC 规定协议对象怎样验证,却不替客户端决定信任库和产品策略。截至 2026 年,Chrome 会根据 Chrome Root Store、Chrome Root Program 规则和用户本地设置判断公共 TLS 信任;Windows 则通过受信与不受信 CTL 更新系统信任。Firefox 在 Windows、macOS 和 Android 上还可以使用操作系统中安装的第三方根,并允许企业策略改变这一行为。因此,同一条证书链可能因浏览器、操作系统版本和企业配置不同而得到不同结论。

邮件客户端还要叠加 S/MIME 配置。Microsoft 文档说明,Outlook 会从受信根位置构建信任,Outlook on the web 需要配置证书集合来验证签名;移动版发送前还会检查证书能否用于签名或加密,并要求每个加密接收者都有可用公钥证书。于是,“CMS 语法正确”“签名数学正确”“客户端显示可信”和“当前可以加密给这个收件人”仍是四个不同判断。

截至 2026 年,S/MIME 4.0 由 RFC 8551 定义,并由 RFC 9788 补充加密邮件头保护。Microsoft Authenticode 和 Apple Developer ID 也都把可信时间用于判断代码签名证书在签署时是否有效,但它们仍叠加各自的信任库、制品格式、平台政策和公证机制。

这说明“同样是 CMS 和 X.509”不代表不同平台可以互换验证结论。对象格式、签名 Profile、时间戳位置、状态策略和平台政策共同决定最终语义。


九、用 OpenSSL 观察 CMS 与时间戳

下面的命令以 OpenSSL 3.6 为参考,只用于理解对象和验证步骤。生产系统仍要固定 Profile、算法、规范化规则和状态策略。

生成并验证内嵌 CMS 签名

openssl cms -sign \
  -in contract.bin \
  -binary \
  -nodetach \
  -md sha256 \
  -signer signer.pem \
  -inkey signer-key.pem \
  -certfile intermediates.pem \
  -outform DER \
  -out contract.p7s

openssl cms -verify \
  -inform DER \
  -in contract.p7s \
  -binary \
  -CAfile trusted-root.pem \
  -purpose any \
  -verify_retcode \
  -out verified-contract.bin
bash

-nodetach 把原文放入签名对象;-binary 禁止 S/MIME 文本换行转换;-verify_retcode 让脚本在验证失败时得到非零退出码。OpenSSL 默认按 S/MIME 签名用途检查证书,而这里演示的是通用 CMS 结构,所以实验命令用 -purpose any 避免误套 S/MIME 用途。生产系统不能把它理解为“跳过用途校验”,而应按电子签名规范另外校验 EKU、证书策略和业务授权,也不要为了让命令成功而使用 -noverify。验证成功后还要按业务要求补充吊销状态和可信签名时间;OpenSSL 3.6 文档也明确指出,cms 命令不会自动对签名者证书执行吊销检查。

生成数字信封

openssl cms -encrypt \
  -in contract.p7s \
  -binary \
  -outform DER \
  -out contract.p7m \
  -aes-256-gcm \
  recipient.pem

openssl cms -decrypt \
  -inform DER \
  -in contract.p7m \
  -recip recipient.pem \
  -inkey recipient-key.pem \
  -out recovered.p7s
bash

这里先签名再加密,接收者解密后得到原始 SignedData。AES-GCM 同时提供内容机密性和密文完整性,接收者证书只负责对应的 CEK 保护机制。

创建并验证时间戳请求

openssl ts -query \
  -data contract.p7s \
  -sha256 \
  -cert \
  -out contract.tsq

# contract.tsr 由 TSA 根据 contract.tsq 返回
openssl ts -verify \
  -queryfile contract.tsq \
  -in contract.tsr \
  -CAfile tsa-root.pem \
  -untrusted tsa-intermediates.pem
bash

OpenSSL 默认会在时间戳请求中加入随机 nonce,除非显式使用 -no_nonce-cert 请求 TSA 在响应中附带签名证书;验证命令则同时核对原请求、响应签名和 TSA 证书路径。真实归档还要保存 TSA 状态证据和适用策略。


十、总结

PKI 进入应用后,不再是一条单独的证书链,而是一组围绕不同对象工作的协议。

TLS 1.3 用证书签名握手 transcript,证明对端控制私钥,再用派生出的对称密钥保护当前连接;CMS SignedData 把签名者和确定内容绑定成可携带对象;数字信封用随机 CEK 高效加密内容,并分别为接收者保护 CEK;RFC 3161 时间戳则由 TSA 把摘要与可信时间绑定。

真正完整的业务证据还需要用户意愿、权限、文件版本、证书状态、验证策略和审计记录。任何一个密码对象都不能独自替代整条证据链。

TLS 保护“这次连接”,文件签名保护“这份内容”,数字信封控制“谁能读取”,时间戳证明“何时已经存在”,业务系统回答“为什么允许这次操作”。

下一篇将从应用回到 CA 运营侧,讨论根密钥和 HSM、机房与网络分区、CP/CPS、备份恢复、审计和业务连续性怎样共同守住整个信任体系。


参考资料

现行资料核验于 2026-08-06。TLS、CMS/S/MIME、时间戳算法和平台代码签名政策仍会变化;生产系统应按目标协议、平台和发布时间重新核验。


系列文章

笔记整理自《PKI、CA 与数字证书技术大全》第 5、19~22 章,并按现行 TLS、CMS/S/MIME、TSP、代码签名平台与 OpenSSL 官方文档校正和补充。


正在加载留言…


上一篇
理解PKI(八):CA 如何安全运营——密钥保护、CP/CPS 与业务连续性
下一篇
理解PKI(六):一张证书如何被信任——路径构建、吊销状态与用途校验

搜索文章...

⌘K / Ctrl K

使用上下方向键选择结果,按 Enter 打开,按 Esc 关闭

搜索结果