跳到內容
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.
一個數位簽章如何被產生、然後被查驗,從第一原理出發。簽署者用其私鑰對內容產生一個簽章——對 RSA 與 ECDSA 而言是對一個雜湊,對 EdDSA 而言則是直接對內容;驗證者重建出同一個輸入,然後對公鑰、簽章與該輸入執行該演算法的驗證運算。一個有效的結果證明那些位元組完好,且是私鑰的持有者簽的。

在一份真實的 PDF 裡,NextPDF 不是把印鑑壓在可見的頁面上——它把印鑑壓在一段宣告的位元組範圍上,並把簽章封裝成一個分離式 CMS 物件(Spec: RFC 5652),置於檔案內部(Spec: ISO 32000-2, §12.8)。有一項細緻之處很重要:簽署者並不直接簽署原始的內容雜湊。它簽署的是一小組已簽署屬性,這組屬性包含內容的雜湊,因此時間、內容類型與簽署者的憑證識別符全都被一同封緘。那些位元組存在何處、以及該範圍為何被塑造成那樣,是 簽章如何坐落於一份 PDF 中 的主題。

到此為止,數學證明了某把金鑰簽了這些位元組。它對是誰的金鑰隻字未提。那道缺口正是一張憑證所填補的。一張憑證本身就是一份已簽署的陳述——來自一個憑證機構——聲明這把公鑰屬於這個具名的主體。信任它意味著沿著從簽署者憑證一路向上、到一個你已決定信任之權威的鏈條,逐一查驗沿途每一個環節的有效性(Spec: RFC 5280, §6)。少了那條鏈,「已簽署」是匿名的。有了它,「已簽署」就有了一個名字。

最後一塊是何時。一個簽章可以嵌入簽署者自己宣稱的時間,但一個你無法掌控的時鐘是一項聲稱,而非一個證明。一個來自時間戳機構(Time-Stamp Authority)的時間戳,把簽章的一個雜湊綁定到一個瞬間,由一個對該文件毫無利害關係的一方加以證實(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:此處所述的雜湊與簽署機制,封裝成一個分離式 CMS SignedData 物件,外加對著一個你提供之信任錨所做的憑證路徑驗證。

Pro

增添 PAdES B-T——在簽章值上加上一個經查驗的 RFC 3161 受信任時間戳,讓那個「何時」是被證實的、而非自行聲稱的。

Enterprise

增添長期設定檔(B-LTB-LTA):嵌入的驗證材料與文件時間戳,讓身分與時間的證明在數年間仍可被回答。

  • 雜湊(摘要)——出自一個密碼學雜湊函式、資料的一個簡短、固定長度的指紋;對資料的任何改動都會把它完全改變。
  • 金鑰對——一把相連的私鑰(只由簽署者持有)與公鑰(自由分享);只有私鑰能簽署,也只有相符的公鑰能驗證。
  • 簽署——對演算法所定義的輸入施加私鑰運算:對 RSA 與 ECDSA 而言是內容的一個雜湊,對 EdDSA 而言則是直接對內容(後者在內部做雜湊)。在一份 PDF 裡,那份內容是已簽署屬性,其中包含文件的雜湊。其結果就是簽章值。
  • 驗證——重建出同一個輸入(一個重新計算的雜湊,或對 EdDSA 而言直接是內容),然後對公鑰、簽章值與該輸入執行該演算法的驗證運算,以取得一個有效或無效的答案。
  • 簽章值——私鑰運算所產生的那些位元組;驗證者拿來對著新鮮重建之演算法輸入(對 RSA 與 ECDSA 而言是一個摘要,對 EdDSA 而言是內容本身)查驗的東西。
  • 憑證——一份把一把公鑰綁定到一個具名身分的已簽署陳述;藉由鏈接到一個你接受的權威而被信任(RFC 5280 §6)。
  • 信任錨——一個你已決定信任的憑證機構;一條可接受之憑證鏈的根。
  • 時間戳(RFC 3161——一個來自時間戳機構(Time-Stamp Authority)的已簽署權杖,把一個雜湊綁定到一個時間值,提供關於何時的受信任證明。
  • CMS SignedData——承載簽章值與簽署者憑證的密碼訊息語法(Cryptographic Message Syntax,RFC 5652)結構。
  • PAdES——PDF Advanced Electronic Signatures:用於 PDF 簽署的 ETSI 設定檔系列。在各簽署頁面有深入涵蓋。