跳转到内容
getnextpdf.com

数字签名如何证明是谁签的

Spec: RFC 5652Spec: RFC 5280, §6Spec: RFC 3161, §1

一个数字签名做三件事,而从一开始就把它们分开来看是值得的:它证明字节没有被改动,它证明是谁签的,以及——在一点点帮助下——它证明何时签的。本页从零开始构建这个概念,好让密码学不再是一个黑盒。

“已签名”是一个人们据以下决定的词。一份合同、一张发票、一份某人即将运行的软件的发行说明。如果你只知道一个工具打印了一个绿色对勾,你其实并不知道证明了什么。你也许有字节完好无损的证据,却完全不知道是谁的密钥签了它们。你也许有一个真实的签署者,但有一张多年前就已过期的证书。理解这些部件,正是让你能够精确地说出一个有效签名意味着什么——以及同样有用地,它意味着什么——的关键。

想象一个防篡改信封,封口处压着一枚个人印章。

  • 这枚印章对一个人来说是独一无二的,而且实际上无法伪造。任何人都能识别它;只有它的拥有者能制作它。那就是密钥对:一把只有你持有的私钥,和一把人人都能看到的公钥。
  • 你不会把印章盖在整份文档之上。你把它盖在它的一份微小、无法伪造的摘要之上——一个哈希。改动文档的一个字节,这份摘要就会彻底改变,于是印章便不再吻合。
  • 签名是用你的私钥在文档之上产生一个签名——对 RSA 与 ECDSA 是在哈希之上,对 EdDSA 则是在内容本身之上,它在内部做自己的哈希。验证是用你的公钥、那个签名以及同一份内容(作为哈希,或直接地)运行该算法的核查——它返回有效或无效。把二者连接起来的是数学,而不是信任。
  • 一张证书是那个说出这枚印章是谁的的部件。没有它,你证明了一把密钥签了那些字节,却没有证明那把密钥属于任何特定的人。
  • 一个时间戳是那个说出何时的部件。签署者自己的时钟只是一个声称;受信任的时间来自一个外部权威机构。

从哈希开始,因为其他一切都立在它之上。一个密码学哈希函数读取任意数量的数据,并产生一个简短、定长的指纹——对一个 PDF 签名来说,通常是一个 256 位的值。两个属性让它有用:同一个输入总是产生同一个指纹,而要找到一个不同的、却有相同指纹的输入则是不可行的。所以一个哈希是文档的一个忠实替身。如果两个哈希吻合,字节就吻合。

现在说密钥对。一把私钥和一把公钥在数学上是关联的,但在任何实际的时间内你都无法从其一推导出另一个。只有私钥能产生一个有效签名,而只有匹配的公钥能核查它。签名利用了那种不对称性:签署者对所选算法定义的输入施加一个私钥操作——对 RSA 与 ECDSA 是内容的哈希,对 EdDSA 则是内容本身,它在内部做哈希。结果就是签名值。因为只有私钥的持有者才可能产生它,又因为它被绑定到这份内容,它一次证明两件事——持有者签了名,且自那以来字节没有动过。

验证镜像了那套逻辑。验证者重建同一个输入——对 RSA 与 ECDSA 是对文档做哈希,对 EdDSA 则是直接取用内容——然后用公钥、签名值以及那个输入运行签名算法的验证操作。这个操作返回有效或无效——而无论算法是 RSA、ECDSA 还是 EdDSA,这都是同一种形态,尽管只有 RSA 才能被想象成字面意义上的“恢复”出哈希。有效意味着完好且真实。无效意味着有东西改变了,或者用错了密钥,而诚实的答案就是无效

  1. Prepare the inputRSA and ECDSA hash the document to a short, fixed-length fingerprint; EdDSA signs the content directly and hashes it internally. Either way, any change to the bytes changes what is signed.
  2. Sign with the private keyThe signer applies a private-key operation to that input — the hash for RSA and ECDSA, the content for EdDSA. The output is the signature value; only the private-key holder could have produced it.
  3. Reconstruct the inputThe verifier independently rebuilds the same input from the content — a recomputed hash for RSA and ECDSA, the content itself for EdDSA. This is what the verify operation checks against.
  4. Verify with the public keyThe verifier runs the algorithm's verify operation on the public key, the signature value, and that input. The same shape works for RSA, ECDSA, and EdDSA.
  5. Accept or rejectThe verify operation returns valid or invalid. Valid means the bytes are intact and the private-key holder signed them. Otherwise the result is invalid — no guessing.
How a digital signature is produced and then checked, from first principles. The signer produces a signature over the content with their private key — over a hash for RSA and ECDSA, or over the content directly for EdDSA; the verifier reconstructs the same input, then runs the algorithm's verify operation on the public key, the signature, and that input. A valid result proves the bytes are intact and that the private-key holder signed them.

在一个真实的 PDF 中,NextPDF 不会把印章盖在可见的页面之上——它把它盖在一段声明的字节范围之上,并把签名打包成一个分离式 CMS 对象(Spec: RFC 5652),放置在文件之内(Spec: ISO 32000-2, §12.8)。有一处细化很重要:签署者并不直接对原始内容的哈希签名。它对一小组签署属性签名,这些属性包含内容的哈希,于是时间、内容类型与签署者的证书标识符就全部被一起封进去。那些字节存放在哪里,以及这段范围为什么是它现在这个形状,是签名在 PDF 中的存放方式所探讨的主题。

到此为止,数学证明了某把密钥签了这些字节。它对是谁的密钥只字未提。那个空白正是一张证书所填补的。一张证书本身就是一份签了名的声明——来自一个证书颁发机构——声明这把公钥属于这个具名的主体。信任它意味着沿着链条从签署者的证书一路向上追溯到一个你已决定信任的权威机构,并沿途核查每一环的有效性(Spec: RFC 5280, §6)。没有那条链,“已签名”是匿名的。有了它,“已签名”就有了一个名字。

最后一块是何时。一个签名可以嵌入签署者自己声称的时间,但一个你无法控制的时钟是一个声称,而不是一个证明。一个来自时间戳机构的时间戳,把签名的一个哈希绑定到一个瞬刻,由一个对该文档没有利害关系的第三方背书(Spec: RFC 3161, §1)。那正是让一个签名在签署者的证书过期之后仍然有意义的东西:你可以表明该签名是在证书仍然有效时就已存在。更深入的论述在时间戳与可信时间中。

下面的形态是那些概念性操作,用白话写出。重点不是一次 API 调用——而是看清签名验证是镜像,以及一个被篡改的字节会从构造上破坏那次吻合。

<?php
declare(strict_types=1);
// SIGN — the holder of the private key seals the content.
// RSA and ECDSA sign a hash; EdDSA signs the content directly (hashing inside).
$signingInput = sign_input($content); // a digest for RSA/ECDSA, the content for EdDSA
$signatureValue = private_key_transform( // only the key holder can do this
privateKey: $signerPrivateKey,
input: $signingInput,
);
// VERIFY — anyone with the public key runs the algorithm's check.
$recomputedInput = sign_input($content); // verifier rebuilds the same input
$intactAndAuthentic = signature_verify( // RSA, ECDSA, EdDSA — same shape
publicKey: $signerPublicKey, // taken from the certificate
signatureValue: $signatureValue,
input: $recomputedInput, // the freshly reconstructed input
);
// true -> bytes unchanged AND signed by the private-key holder
// false -> a byte changed, or the wrong key — the honest answer is "invalid"
// The certificate answers a SEPARATE question: whose public key is this?
// The timestamp answers ANOTHER: when did this signature exist?
// "intact + authentic" alone proves neither identity nor time.

伪造一处仍能通过验证的改动,在计算上是不可行的。哈希忠实地承载内容,所以改动内容就会改动哈希,而验证操作会拒绝它——除非找到一次哈希碰撞,而所选函数的设计正是为了让这件事变得不切实际。

一个常见的错误,是把“有效签名”读成“可信文档”。它们不是同一句话。密码学证明的是字节完好无损以及私钥持有者签了它们——仅此而已。一份用你从未听说过的密钥签署、没有任何你认可的权威机构背书的文档,可以是完美“有效”的,却一文不值。身份来自证书及其链条;时间来自一个受信任的时间戳。一个把所有这些悄悄叠进一个布尔值的绿色对勾,已经替你决定了哪些问题重要。知道这些部件,正是让你能够去问其余问题的关键。

本页解释的是一个数字签名的概念,而不是完整的验证流程。这里的数学证明了完整性与真实性。它不会单凭自身告诉你,那张签署证书是否颁发给了你以为的那个人、它在签署时是否有效,或者它后来是否被吊销——那些是证书路径与吊销方面的问题,而一次正确的验证会把它们全部跑一遍。完整的核查集合在正确验证签名中。

NextPDF 构建签名结构并执行密码学核查。它不会替你选择信任锚点,不会为任何证书颁发机构作担保,也不会裁定一个签名的法律效力——那些取决于你的部署、那张证书,以及管辖区。引擎证明那套机制;建立在其之上的信任决定则属于你。

引擎按层级所交付的内容,从这个基础向外构建:

Digital signature: integrity, identity, and trusted time — edition availability
EditionAvailability
Core

PAdES B-B: the hash-and-sign mechanism described here, packaged as a detached CMS SignedData object, plus certificate-path validation against a trust anchor you supply.

Pro

Adds PAdES B-T — a verified RFC 3161 trusted timestamp on the signature value, so the “when” is attested rather than self-claimed.

Enterprise

Adds the long-term profiles (B-LT, B-LTA): embedded validation material and document timestamps that keep the identity and time proofs answerable for years.

  • 哈希(摘要)——来自一个密码学哈希函数的、数据的简短、定长指纹;对数据的任何改动都会彻底改变它。
  • 密钥对——一把关联的私钥(仅由签署者持有)与公钥(自由共享);只有私钥能签名,而只有匹配的公钥能验证。
  • 签名——对算法所定义的输入施加私钥操作:对 RSA 与 ECDSA 是内容的一个哈希,对 EdDSA 则是内容本身(它在内部做哈希)。在一个 PDF 中,那个内容是签署属性,其中包含文档的哈希。结果就是签名值。
  • 验证——重建同一个输入(一个重新计算的哈希,或对 EdDSA 是内容本身),然后用公钥、签名值以及那个输入运行算法的验证操作,以得到一个有效或无效的答案。
  • 签名值——私钥操作所产生的那些字节;一个验证者用以核查新鲜重建出的算法输入(对 RSA 与 ECDSA 是一个摘要,对 EdDSA 则是内容本身)的东西。
  • 证书——一份把公钥绑定到一个具名身份的签名声明;通过链接到一个你认可的权威机构来获得信任(RFC 5280 §6)。
  • 信任锚点——一个你已决定信任的证书颁发机构;一条可接受的证书链的根。
  • 时间戳(RFC 3161——来自一个时间戳机构的签名令牌,把一个哈希绑定到一个时间值,提供关于何时的受信任证明。
  • CMS SignedData——承载签名值与签署者证书的密码消息语法(Cryptographic Message Syntax,RFC 5652)结构。
  • PAdES——PDF Advanced Electronic Signatures:用于 PDF 签名的 ETSI 配置族系。在签署相关页面中深入涵盖。