コンテンツにスキップ

PDF における署名の配置

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

PDF の署名は、ファイルの外側を包み込むものではありません。ファイルの 内部 に埋め込まれます。署名を識別するディクショナリと、署名値そのものを意図的に除外した、宣言済みのバイト範囲全体に対して計算されるダイジェストから成ります。このページでは、そのメカニズムと、同じくらい重要な、それが約束しないことを説明します。

「この文書は署名されている」という一文を根拠に、人は行動します。支払い、承認、法的義務と結び付けるからです。署名がどのバイトをカバーしているかを正確に把握していなければ、「有効」という結果が実際に何を証明しているのかは述べられません。PDF は、完全に有効な署名を持ちながら、署名者が一度も見ていないコンテンツを読者に表示することがあります。そのコンテンツが署名 に、署名が何も主張していない領域へ追加されたためです。署名の権限がどこで始まりどこで終わるのかを知ることが、根拠をもって擁護できる判断と、希望的観測にすぎない判断との違いになります。

  • PDF の署名は、外部の封筒としてではなく、文書内の 署名ディクショナリ署名フィールド に存在します。
  • 署名対象のバイトは ByteRange 配列で宣言されます。これは 2 つの (offset, length) セグメントで、両者を合わせてファイル全体をカバーしますが、Contents エントリに保持される 16 進の署名値 だけ は除きます。
  • この 2 つのセグメントを連結したもののダイジェストが、暗号署名で実際に保護される対象です。
  • 後から新しいリビジョンとして追記されたものはすべて、元のバイト範囲の 外側 にあります。元の署名は有効なまま維持されます。新しいバイトについて何も主張していないからです。
  • 承認 署名と 認証 署名はスコープが異なります。認証(DocMDP)は後からの変更として何が許されるかを制約しますが、承認はそうしません。

NextPDF は、フォーマットが意図する固定された順序で署名を構築するため、バイト範囲は近似ではなく正確なものになります。

エンジンが署名を書き込む際は、まず固定サイズのスロットを予約します。 Contents の値を予約し、固定幅の ByteRange のプレースホルダーを書き込みます。エンジンは、 相互参照テーブルとファイル終端マーカーを含む文書全体が書き込まれるまで待ちます。そこで初めて、2 つの実際のオフセットを計算し、 いかなるバイトも動かさずにプレースホルダーへ書き戻し、2 つのセグメントをハッシュし、結果として得られる 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 プレースホルダー、2 つのセグメントを連結したダイジェスト、予約済みの Contents スロット内に置かれるデタッチド CMS オブジェクトで構成され、ファイルが完成して初めて確定されます。

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.

2 つのセグメントの間の隙間が署名値です。署名値が自分自身のダイジェストの一部になることはできません。だからこそ範囲は 1 つではなく 2 つの断片になります。

落とし穴は、有効な署名があるなら いま見ているファイル全体 が署名済みだと信じ込むことです。そうではありません。それが意味するのは、宣言された範囲内のバイトが無傷であるということです。後のリビジョンでは、その範囲の 外側 に、第 2 の署名、フォームデータ、検証用マテリアルといったコンテンツを正当に追記できます。最初の署名は有効なまま維持され、その追加については何も述べません。正しいビューアは、署名が「画面上のあらゆるバイト」ではなく「署名時点で存在していた文書」をカバーしていると伝えます。この 2 つを同一視することが、署名済み文書に、署名されたように見える未署名コンテンツを取り込ませる経路になります。

このページが説明するのは 構造 であって、信頼 ではありません。正しく形成された 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)を追加します。埋め込まれた検証用マテリアルと文書タイムスタンプを、同じバイト範囲の基盤の上に重ねたものです。

  • 署名ディクショナリ — 署名ハンドラー、SubFilterByteRange、そして Contents の値を指定する PDF ディクショナリ。
  • ByteRange — 署名ダイジェストがカバーする正確なバイトを宣言する、(offset, length) 整数ペアの配列。
  • Contents — 署名値を保持する 16 進エントリ(PDF 2.0 では、デタッチド CMS SignedData オブジェクト)。自分自身のダイジェストからは除外されます。
  • CMS SignedData — 署名者の証明書と署名バイトを保持する、暗号メッセージ構文(RFC 5652)構造。
  • PAdES — PDF Advanced Electronic Signatures。ETSI EN 319 142 シリーズで定義された、PDF 向けの CMS 署名の ETSI プロファイル。
  • 承認署名MDP パーミッションが 0 の署名。後からの変更を制約せずに、コンテンツについて主張します。
  • 認証署名DocMDP パーミッション(MDP 13)を持つ署名。後続の変更のうちどれが文書を準拠状態に保つかを制限します。