PDF署名検証 開発者が必要な知識
多くの開発者は、暗号化されたPDFファイルについてその暗号署名が実際に保証していることについて理解していない。正確にPDF署名を検証するためには、以下の情報が必要です。
多くの開発者は署名付きPDFファイル(契約書、公的文書、銀行の確認書類など)と接しているでしょう。しかし「署名」という言葉は誰が話すかによって意味が異なります。署名ボックスに記された視覚的な文字と、ファイルに埋め込まれた暗号化署名はまったく異なるものです。その中で、どちらかが実際に信頼性を保証します。
このガイドでは、PDFデジタル署名の仕組み、異なる有効性状態の意味、そしてプログラムでその検証方法について説明します。
見える署名と重要な署名
誰かがPDFに描画ツールまたは画像スタンプを使って「署名」をすると、そのページに署名の画像が貼り付けられます。見た目は正式ですが、セキュリティ上の保証はありません。誰でもその画像を別のドキュメントにコピーできます。
デジタル署名は異なります。それはPDF構造自体に埋め込まれた暗号化されたアーティファクトです。視覚的な要素とは別です。ドキュメントがデジタル署名されると:
- ドキュメントの内容に対してハッシュ値が計算されます。
- そのハッシュ値は署名者の秘密鍵で暗号化されます。
- 暗号化されたハッシュ(署名)は、署名者の証明書チェーンとともにPDFに保存されます。
署名を検証する際には、証明書から公開鍵を使って署名を復号し、現在のドキュメントの内容からハッシュ値を再計算し、比較します。一致すれば、署名以降ドキュメントは変更されていません。一致しない場合、または証明書が信頼されていない場合、署名は無効です。
4つの署名有効性状態
すべてのデジタル署名が同じとは限りません。PDF署名を検証する際に、次の4つの状態のいずれかに該当します:
| 署名状態 | 意味 | 対応策 |
|---|---|---|
| 有効 | ハッシュ値が一致し、証明書チェーンが信頼でき、署名時には有効であった | 署名を信頼し、署名者の身分を期待される証明書と照合してください |
| 無効 | ドキュメントの内容が署名後変更された、または署名データが破損している | ドキュメントを拒否してください。改ざんされたか、形式が不正です |
| 不明 | 署名構造は完備していますが、証明書が検証できません(信頼されていないルート、OCSPが欠落しているなど) | 署名を信頼できません。再署名を要求するか、信頼できるルート証明書を取得してください |
| 取り消し | 発行時に有効であったが、CAによって後に取り消された(鍵の漏洩など) | 取り消し前に証明書が有効であったことをLTVデータが証明しない限り、拒否してください |
「不明」の状態は多くの開発者を混乱させます。自署名または企業内でのみ使用される証明書を持つ署名は、多くのツールで「不明」と表示されます。その理由は、その発行者に到達できないためです。内部ドキュメントのワークフローでは、そのルートを明示的に信頼できますが、外部からのドキュメントでは「不明」は受け入れできません。
長期検証(LTV):タイムスタンプの重要性
証明書は期限切れになります。5年前に署名されたドキュメントで、その証明書が現在期限切れになった場合、署名はまだ有効ですか?
それは長期検証(LTV)に依存します。LTVが埋め込まれている場合、PDFには次の情報が含まれます:
- 信頼できるタイムスタンプ局(TSA)からの信頼できるタイムスタンプ
- 署名時の証明書の有効性を確認するOCSP応答またはCRLデータ
LTVがある場合、署名が適用された時点での証明書の有効性を証明できます。LTVがなければ、現在の証明書の有効性に基づいて検証でき、証明書が期限切れになったり、OCSPレスポンダーが停止された場合、検証は不可能になります。
契約書や規制上の文書で長期保存(多くの法域では7年以上)が必要な場合、LTVはオプションではありません。署名検証ワークフローを構築する際には常にLTVの有無を確認してください。
プログラムによるPDF署名の検証
Pythonとpypdfを使用
の pypdf ライブラリはPDF署名フィールドおよびその下位メタデータにアクセスできます。次の最小限の例は、PDFにデジタル署名が存在するかを確認し、その状態を読み取るものです:
import sys
from pypdf import PdfReader
def check_pdf_signatures(path: str) -> None:
reader = PdfReader(path)
sig_fields = [
name for name, field in (reader.get_fields() or {}).items()
if field.get("/FT") == "/Sig"
]
if not sig_fields:
print("No digital signature fields found.")
return
print(f"Found {len(sig_fields)} signature field(s):")
for name in sig_fields:
sig_obj = reader.get_fields()[name]
sig_dict = sig_obj.get("/V")
if not sig_dict:
print(f" {name}: field present but unsigned")
continue
signer_name = sig_dict.get("/Name", "Unknown")
signing_time = sig_dict.get("/M", "No timestamp")
reason = sig_dict.get("/Reason", "")
location = sig_dict.get("/Location", "")
print(f" {name}:")
print(f" Signer: {signer_name}")
print(f" Time: {signing_time}")
if reason:
print(f" Reason: {reason}")
if location:
print(f" Location: {location}")
if __name__ == "__main__":
check_pdf_signatures(sys.argv[1])
これはPDF構造から署名メタデータを直接読み取ります。完全な暗号検証(ハッシュの確認と証明書チェーンの検証)を行うには pyhanko または endesive、どちらもPKCS#7検証レイヤーをラップしています。
qpdfをコマンドラインで使用
コマンドラインで迅速に確認できる場合、Python環境をセットアップする必要がありません:
# Show encryption and signature info
qpdf --show-encryption input.pdf
# Full JSON output with signature details
qpdf --json input.pdf | python3 -m json.tool | grep -A 20 '"sig"'
qpdf CIパイプラインやシェルスクリプトで特に有用です。Python仮想環境を設定する必要がなく、効率的です。
一般的な検証シナリオ
クライアントからの契約書 — 有効性状態と証明書発行者を確認します。信頼できないルート(自署名証明書)からの有効な署名は外部の保証を提供しません。鍵を生成した人を信頼しています。
政府および規制文書 — これらは国家レベルで信頼される証明書機関を使用します。証明書チェーンが期待されるルートCAに結びついていることを確認してください。単に「有効」というだけでは不十分です。
銀行のステートメントやインボイス — これらは組織に発行されたドキュメント署名証明書を使ってブロックで署名されます。署名者の名前は機関名になります。個人の従業員ではなくなります。
コードを書かずに検証
開発環境を構築せずにすぐにPDF署名を検証したい場合、 IO Tools PDF署名チェックツール を使用できます。ファイルをアップロードして、署名の詳細をすぐに確認できます。一時的な検証や、生産パイプラインを構築する前にサンプルファイルをテストする際に便利です。
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
