跳到內容

PDF 中簽章的存在方式

Spec: ETSI EN 319 142-1 Spec: RFC 5652 Evidence: Standard-backed

PDF 簽章不是包覆在檔案外層,而是內嵌在檔案內部:一個為簽章命名的字典,以及一段針對所宣告位元組範圍計算的摘要,而該範圍刻意略過簽章值本身。本頁會說明這套機制;同樣重要的是,也會說明它沒有承諾什麼。

「這份文件已簽署」是一句會讓人據以行動的話。他們會把它連結到一筆付款、一項核准、一份法律義務。若你無法精確知道某個簽章涵蓋哪些位元組,就無法說清楚一個有效結果究竟證明了什麼。一份 PDF 可以帶著完全有效的簽章,卻仍向讀者顯示簽署者從未見過的內容,因為那些內容是在簽署之後才加入到簽章從未宣告涵蓋的區域。知道簽章權威性的起點與終點在哪裡,正是一個站得住腳的決定,與一個只能寄望沒有出錯的決定之間的差別。

  • PDF 簽章存在於文件內部的簽章字典簽章欄位之中,而不是外部封套。
  • 已簽署的位元組由 ByteRange 陣列宣告:兩段 (offset, length) 區段合起來涵蓋整個檔案,唯獨排除存放在 Contents 項目中的十六進位簽章值。
  • 這兩段串接區段的摘要,正是密碼簽章實際保護的對象。
  • 之後在新修訂版中附加的任何內容,都位於原始位元組範圍之外。原始簽章仍然有效;它從未對那些新位元組做出任何宣告。
  • 核准簽章與認證簽章在範圍上有所不同:認證(DocMDP)會限制日後允許哪些變更;核准則不會。

NextPDF 會依照格式設計的方式,以固定順序建立簽章,讓位元組範圍精確而非近似。

當引擎寫入簽章時,它會先為 Contents 值保留一個固定大小的槽位,並寫入一個固定寬度的 ByteRange 佔位符。接著,它會等到整份文件寫入完成——包含交叉參照表與檔案結尾標記。唯有此時,它才會計算兩個真正的偏移量、 在不移動任何位元組的情況下將其寫回佔位符、對這兩段區段進行雜湊,並將產生的 CMS 物件放入保留的槽位。這個佔位符會以零填補至固定長度,目的正是讓填入真實數字時不會挪動正在被雜湊的那些位元組。只有這樣的順序才能產生自洽的簽章。引擎會將此序列中的任何失敗視為硬性錯誤,而非靜默退回。

就 PDF 2.0 設定檔而言,簽章物件本身是一個分離式 CMS SignedData 結構。 PDF 字典說明位置方式;CMS 物件則承載由誰簽署以及密碼證明。

  1. Step 1 of 4: ISO 32000-2 §12.8.1 — ByteRange digest & signature dictionary
  2. Step 2 of 4: ISO 32000-2 §12.8.3.3 — ETSI.CAdES.detached SubFilter
  3. Step 3 of 4: ETSI EN 319 142-1 PAdES baseline profile
  4. Step 4 of 4: RFC 5652 CMS SignedData in Contents
PDF 簽章定義之處,從容器格式一路向下到密碼物件:ISO 32000-2 規範了字典與位元組範圍機制,ETSI EN 319 142-1 將其設定為 PAdES 設定檔,而 RFC 5652 則定義了置於 Contents 中的 CMS SignedData 物件。

Evidence: Standard-backed 此機制定義於 Spec: ISO 32000-2, §12.8.1 。位元組範圍摘要會依照 ByteRange 項目所指示的位元組範圍計算。該範圍應為整個檔案,包含簽章字典但排除 簽章值——也就是 Contents 項目。ByteRange 是一個由整數配對組成的陣列——起始偏移量與長度。採用不連續範圍正是為了讓摘要能略過簽章值本身。

就 PDF 2.0 設定檔而言, Spec: ISO 32000-2, §12.8.3.3 規範了當 SubFilterETSI.CAdES.detached 時,Contents 值是一個 DER 編碼的 CMS SignedData 物件——與 Spec: RFC 5652 所定義者相同——而該物件的 PAdES 設定檔也就是 Spec: ETSI EN 319 142-1 所描述的設定檔。

不同簽章的範圍不一定相同。 Spec: ISO 32000-2, §12.7.4.5 定義了 MDP 權限:值為 0 時,該簽章為核准簽章,而值 13 則使其成為認證簽章,會限制日後哪些修改仍能讓文件保持合規。相同的位元組範圍機制;對未來的承諾卻不同。

NextPDF 的引擎正是以這種方式實作:一個固定寬度的 ByteRange 佔位符、兩段串接的摘要,以及一個分離式 CMS 物件,置於保留的 Contents 槽位中,唯有在檔案完成後才會最終確定。

你很少會手動建構 ByteRange。這個範例的重點是展示結果的形狀,讓你在檢視已簽署檔案時能夠辨認出來。

<?php
declare(strict_types=1);
use NextPDF\Security\Signature\ByteRangeCalculator;
// Offsets the engine knows only after the whole PDF is written:
// $contentsStart — byte just before the '<' of the hex signature
// $contentsEnd — byte just after the '>' that closes it
// $fileLength — total file size in bytes
$range = ByteRangeCalculator::calculate(
contentsStart: $contentsStart,
contentsEnd: $contentsEnd,
fileLength: $fileLength,
);
// $range === [0, $contentsStart, $contentsEnd, $fileLength - $contentsEnd]
// Segment 1: file start → just before the signature value
// Segment 2: just after the signature value → end of file
// The signature value itself is the gap. It is never hashed.
$signedMessage = ByteRangeCalculator::extractSignedData($pdfBytes, $range);
// $signedMessage is segment 1 concatenated with segment 2 — exactly the
// bytes the cryptographic digest is computed over.

兩段區段之間的空隙就是簽章值。它不能成為自身摘要的一部分,這也正是範圍必須分成兩段、而不是一段的原因。

常見陷阱在於誤以為一個有效簽章意味著你正在檢視的整份檔案就是當初被簽署的內容。並非如此。它意味著所宣告範圍內的位元組完好無損。日後的修訂版可以正當地將內容——第二個簽章、表單資料、驗證材料——附加到該範圍之外。第一個簽章仍然有效,而它對該新增內容隻字未提。一個正確實作的檢視器會告訴你,某個簽章涵蓋的是「簽署當下存在的那份文件」,而非「螢幕上的每一個位元組」。把這兩者混為一談,正是一份已簽署文件取得看似已簽署、實際上未簽署內容的途徑。

本頁說明的是結構,而非信任。格式正確的 ByteRange 與 CMS 物件會告訴你位元組完好無損,以及是哪把金鑰簽署了它們。但它們本身並不會告訴你那把金鑰是否屬於你所認定的那個人、其憑證在簽署時是否有效,或它事後是否遭撤銷。那屬於憑證路徑與撤銷的工作,涵蓋於 正確驗證簽章。 本頁也不涵蓋簽署在何時發生,是否有任何獨立權威佐證。自我聲稱的簽署時間並非受信任時間—— 請見 時間戳記與受信任時間。 NextPDF 建構此處所述的結構;憑證、信任錨點、 以及時間戳記權威機構則由你的部署環境提供,而非由引擎提供。

依方案層級,引擎交付的是結構建構能力:

PAdES signature structure (byte range, signature dictionary, detached CMS) — edition availability
Edition Availability
Core

PAdES B-B:簽章字典、固定寬度的 ByteRange,以及本頁所述的分離式 CMS SignedData 物件。

Pro

新增 PAdES B-T——對簽章值附加受信任時間戳記——疊加於相同的結構之上。

Enterprise

新增長期設定檔(B-LTB-LTA):內嵌的驗證材料與文件時間戳記,疊加於相同的位元組範圍基礎之上。

  • 增量更新及其重要性 ——為何採用附加而非重寫,是讓第一個簽章的位元組範圍保持完好的關鍵。
  • PAdES 基線設定檔 ——哪些內容會疊加在這個結構之上,以及某項義務需要哪一種設定檔。
  • 長期驗證 ——驗證證據如何被內嵌,使簽章在多年後仍可驗證。
  • 簽章字典 —— 列出簽章處理常式、SubFilterByteRangeContents 值的 PDF 字典。
  • ByteRange —— 一個由 (offset, length) 整數配對組成的陣列,宣告簽章摘要所涵蓋的確切位元組。
  • Contents —— 存放簽章值的十六進位項目(就 PDF 2.0 而言,是一個分離式 CMS SignedData 物件);它被排除在自身摘要之外。
  • CMS SignedData —— 密碼訊息語法(RFC 5652)結構,承載簽署者的憑證與簽章位元組。
  • PAdES —— PDF 進階電子簽章:ETSI 為 PDF 制定的 CMS 簽章設定檔,定義於 ETSI EN 319 142 系列標準中。
  • 核准簽章 —— 帶有 MDP 權限 0 的簽章;它會聲明內容,但不限制日後的變更。
  • 認證簽章 —— 帶有 DocMDP 權限(MDP 13)的簽章,會限制日後哪些修改仍能讓文件保持合規。