【ITニュース解説】Detect a Tampered PDF in Python Without the Original
2026年09月11日に「Dev.to」が公開したITニュース「Detect a Tampered PDF in Python Without the Original」について初心者にもわかりやすく解説しています。
ITニュース概要
元のPDFがなくても、改ざんされたか判定する方法がある。PDF内部のメタデータ、構造、デジタル署名に残る変更履歴をPython(pypdf, pikepdf)で読み取る。ただし、自己判断は誤検知が多く、正確な判断には専門知識やAPIの活用が推奨される。
ITニュース解説
デジタル文書の信頼性を確認することは、システムエンジニアにとって重要な課題の一つである。特に、銀行の明細書や契約書、署名済みの応募書類など、外部から送られてくるPDFファイルが本物かどうか、改ざんされていないかを判断する必要がある場面は多い。通常、このような確認作業では「元のファイルがなければ、何が変更されたかは分からない」と言われることが多いが、これは半分だけ真実である。PDFファイルそのものが、その履歴に関する証拠を内部に持っているため、必ずしも元のファイルがなくても、作成後に何らかの変更が加えられたかどうかを検出できる可能性がある。
この方法論は、PDFの構造を読み解くことで、ファイルが最初に作成された後に修正された証拠を見つけ出すことに焦点を当てている。これは、書類に記載された数字の事実確認や、発信元の身元確認とは異なる種類の検証である。PDFは、単にページの見た目を記録した「平坦な画像」ではない。それは、複数の層にわたる記録保持機能を持つ、構造化されたコンテナであり、編集ツールは文書の内部構造にその「足跡」を残す。Pythonでは、pypdfやpikepdfといったライブラリを利用することで、これらの内部信号を読み取ることができる。
PDFの改ざんの兆候は、主に三つの層から読み取ることができる。
一つ目の層は「メタデータ」である。PDFファイルには、作成者、どのソフトウェアで作成されたか、いつ作成・変更されたかなどの情報を含む「Infoディクショナリ」や「XMPメタデータ」という付帯情報が格納されている。特に注目すべきは「Creator(作成者)」と「Producer(生成者)」、そして「CreationDate(作成日時)」と「ModDate(変更日時)」の二組のフィールドである。
「Creator」は、人間が文書を作成するために使用したアプリケーション(例: Microsoft Word、Adobe InDesign)を示す。一方、「Producer」は、実際にPDFのバイトデータを書き出したライブラリやエンジン(例: PDF変換ツール、印刷ドライバー)を示す。正規に一度で作成された文書では、これら二つの情報が整合性の取れた物語を語るはずだ。しかし、もしファイルが編集ツールを経由した場合、「Producer」は編集ツールの名前に変わることが多く、一方で「Creator」は元の作成アプリケーションのままである、といった不一致が見られることがある。これは改ざんの兆候として捉えられる。
また、「CreationDate」は文書が最初に作成された日時を、「ModDate」は最後に保存された日時を示す。一度きりで生成されたファイルでは、これらはほぼ同じ瞬間を示すはずだ。もしこれらが大きく乖離している場合、あるいは最悪の場合「ModDate」が「CreationDate」よりも過去の日時を示している場合は、ファイルが作成後に何らかの変更を受けたことを強く示唆している。特に、ファイルが存在する前に保存されたという状況は、正直なワークフローではあり得ない。さらに、Infoディクショナリの日付とXMPメタデータの日付が異なる場合も、どちらかが書き換えられた可能性があり、詳しく調べる価値がある。
ただし、メタデータは最も簡単に偽装できる情報であるという重要な注意点がある。スクリプトを使えば、表示上のピクセルに一切変更を加えることなく、生成者フィールドを上書きしたり、タイムスタンプを過去に戻したりすることが容易に可能だ。そのため、メタデータはあくまで改ざんの「手がかり」であり、最終的な「決定」ではない。だからこそ、次に説明する構造的な層がより重要となる。
二つ目の層は「構造」である。PDFは、内部のオブジェクト(テキスト、画像、フォントなど)がどこに配置されているかを「クロスリファレンステーブル(xref)」という索引で管理している。PDFファイルを保存する際、多くの場合、ファイル全体を最初から書き直すのではなく、変更内容をファイルの末尾に追記する「インクリメンタルアップデート」という方法が用いられる。この方式では、変更が加わるたびに新しいxrefセクションがファイルに付加され、追加されたオブジェクトを指し示す。元のバイトデータはそのまま残り、編集内容がその後に「ボルトオン」される形だ。
この特性は、フォレンジック(鑑識)において非常に有用な意味を持つ。つまり、保存されるたびに独自のxref層が追加されるため、一度生成されてから一度も触られていないファイルにはxrefセクションが一つだけ存在することが多いが、何度か開かれて編集・再保存されたファイルには、xrefの連鎖が蓄積される。この層の数を数えることで、ファイルがどれだけ書き込まれたかをおおよそ把握できる。これは、元のファイルがなくても履歴がファイル自体に残っているため、検出が可能なのである。pikepdfのようなライブラリを使えば、PDFのバージョン情報とともに、このような構造情報を簡単に読み取ることができる。例えば、生データ中のstartxrefというマーカーの数を数えることで、保存された世代数を推定する手法がある。
しかし、ここにも注意点がある。複数の保存世代が存在すること自体が、不正行為の証明になるわけではない。銀行のシステムがファイルを最適化する(リニアライズ)際や、文書管理システムがファイルを処理する際に、正当な理由で構造が追加されることがある。インクリメンタルアップデートは、PDFが本来想定されている成長の仕方でもあるのだ。この信号が示すのは「このファイルは作成後に書き込みが行われた」という事実であり、「悪意を持って編集された」と直ちに断定できるわけではない。その事実が何を意味するかは、期待される文書の種類や、発行元が通常使用するソフトウェアの種類に大きく依存する。
三つ目の層は「デジタル署名」である。もしPDFにデジタル署名が施されている場合、その署名はファイルの特定のバイト範囲を保護している。署名されたバイト範囲の後に何らかのデータが追加された場合、その追加された内容は署名の暗号的保証の対象外となる。
デジタル署名の存在は、PDFの構造を解析することで検出できる。署名フィールドは、PDFのAcroFormディクショナリ内に特定のフィールドタイプ(/Sig)として存在している。この構造から、ファイルに署名フィールドがあるかどうか、そしてそれが有効な署名を含んでいるかどうかを確認できる。特に重要なのは、署名後に変更が加えられたかどうか、そして署名が意図的に削除されたかどうかである。もし署名されたバイト範囲がファイルの末尾まで達しておらず、その後にデータが追加されている場合、署名がファイル全体をカバーしていないことになる。また、署名がされていたはずのファイルから署名が剥ぎ取られ、その痕跡だけが残されている場合は、改ざんの非常に強い兆候となる。これらの検出は、単にメタデータを読み取るよりも複雑で、署名のバイト範囲を解析し、その後の構造を検証する必要がある。
ここまで述べたような改ざん検知の原理は、どれも実用的で有用な情報を提供する。しかし、これらを単独で「改ざんチェック」として運用しようとすると、多くの「偽陽性(誤検知)」に悩まされることになる。
その主な理由は、正規のツールも不正を行う者と同じようなフィールドに手を加えるためである。例えば、全く問題のない請求書が生成された後、サーバー側の最適化ツールによってxref層が追加され、生成者情報が書き換えられることは珍しくない。「生成者が変更されたから改ざんだ」というルールを適用すると、正当な文書の多くを誤って拒否してしまうことになるだろう。重要なのは、どの作成者、生成者、構造、日付の組み合わせが、特定の種類の文書にとって正常で、どれが異常であるかを判断することだ。
このような判断を下すためには、「既知のツールのデータベース」が必要となる。つまり、特定の銀行の明細書生成エンジンと、一般的なPDFエディタ、オンラインのマージツールなどの違いを識別するには、それぞれのソフトウェアの「指紋」と、それがPDFにどのような影響を与えるかを記録した、常に更新されるデータベースが不可欠である。このデータベースの構築と維持は、それ自体が専門的な業務となる。
さらに重要な限界として、そもそも改ざんではなく、最初から偽の文書が生成された場合がある。例えば、不正を行う者が既存の文書を編集するのではなく、ゼロから偽の銀行明細書を作成したり、デザインツールを使って好きな数字で書類を生成したりする場合だ。このようなファイルは、構造的には完全に「無傷」である。最初から偽物として生まれたため、作成後の変更を検出するという構造的な改ざん検知では、これを見破ることはできない。このような文書に対しては、「構造的には確認できない」と正直に判断し、発行元の組織に確認をエスカレートすることが適切な対応となる。
これらの限界を考慮すると、慎重なツールは通常、三つの結果を報告する。「改ざんなし(intact)」、「改ざんあり(modified)」、そして「判断不能(inconclusive)」である。三つ目の「判断不能」は失敗ではなく、文書が消費者向けソフトウェアやオンラインエディタ、スキャナーなどで作成されたため、構造的な整合性チェックが適用できないことを意味する。これは、人による確認や外部チェックが必要な状況を正確に示唆する、正直な回答となる。
もし、このようなツールごとの「指紋データベース」を自前で構築・維持したくないのであれば、専門のフォレンジックサービスを利用するという選択肢がある。HTPBEのような公開APIは、上記で説明したような構造解析、メタデータ解析、署名解析に加えて、偽陽性を抑えるための「既知のツールデータベース」を活用し、単一の「判定(verdict)」を返してくれる。これにより、開発者は複雑な検出ロジックを自力で実装・運用する手間を省き、APIの提供する信頼性の高い結果に基づいて、システム内のワークフローを構築できる。
APIを利用する場合、PDFファイルの公開URLをサービスに送信し、分析を依頼する。その後、その分析結果をIDを使って取得する。結果には「status(状態)」として「intact」「modified」「inconclusive」のいずれかが返され、さらに「modification_confidence(改ざんの確信度)」や、検出された改ざんの種類を示す「modification_markers(改ざんの兆候)」といった詳細情報も含まれる。これにより、例えば「statusがmodifiedであれば拒否する」「inconclusiveであれば発行元に確認をエスカレートする」といった明確な判断基準をコードに組み込むことが可能になる。
PDFは、その内部構造に自身の履歴を証言する多くの情報を含んでいる。たとえ元のファイルが手元になくても、メタデータ層(作成者と生成者の違い、作成日時と変更日時の違い)、構造層(xrefテーブルやインクリメンタルアップデートによる保存世代数)、署名層(署名後の変更や署名の剥奪)といった情報を読み解くことで、ファイルが作成後に変更された証拠を見つけ出すことができる。pypdfやpikepdfといったPythonライブラリを使えば、これらのフィールドを数行のコードで読み出すことができ、簡単な調査であれば十分な情報が得られるだろう。
しかし、これらの信号を読み取ること(技術的な解析)と、その信号に基づいて安全かつ信頼できる「判定」を下すこと(実用的な判断)は異なる。どのツールの組み合わせが正常で、どの組み合わせが異常なのかを判断し、偽陽性を抑え、また構造的な検証だけでは確認できない文書を正確に識別するには、専門的な知識と、常に更新される「既知のツールのデータベース」が不可欠となる。システムエンジニアとして、自身のユースケースにおいて、単に信号を読み取ることが必要なのか、それとも信頼できる最終的な判定が必要なのかを明確に理解した上で、適切なアプローチを選択することが重要である。