Delphiでの本物のPDF墨消し(Redaction)とは、単に上から黒く塗りつぶすことではなく、元のデータをバイトレベルで物理的に削除することを意味します。losLab PDF Libraryは、2つのAPIでこの機能を実現しています。RedactRegionはページのコンテンツストリームを編集し、削除されたテキスト、ベクター、および画像を物理的に消去します。そして、SanitizeDocumentは、墨消しされた情報が隠されている可能性のあるメタデータ、スクリプト、およびアクションをクリアします。このいずれかの処理に不備があると、画面上では検閲(墨消し)されたように見えても、テキストをコピーしたり「文書のプロパティ」を開いたりするだけで機密情報が簡単に復元できてしまうファイルを公開することになります
このような墨消しの失敗例は数多く公表されています。新聞社、裁判所、政府機関などが、名前や数値の上に黒い長方形を重ねただけのPDFを公開し、閲覧者がその長方形の下にあるテキストを選択してコピーし、墨消しされたはずの文字列をそのまま貼り付けて読み取ってしまうという事案が発生しています。この場合、黒い長方形は単なる描画オブジェクトであり、テキストデータは描画層の1つ下のコンテンツストリームに完全に残っており、選択可能な状態のままでした。TPDFlibクラスを介して提供されるDelphiおよびC++Builder用のPDFエンジンであるlosLab PDF Libraryは、この違いこそが墨消し処理の本質であると考え、設計されています
PDFテキストの上に黒いボックスを描画することが真の墨消しではないのはなぜですか?
losLab PDF Libraryは、透かし、視覚的なマスク、校正スタンプなど、純粋に「上から覆い隠す」ことを目的とするユースケース向けにMaskRect提供しています。MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue)は、ページのコンテンツストリームに長方形を追加し、reパス構築演算子とそれに続くf塗りつぶし演算子(ISO 32000-1 §8.5)を使用してそれを描画します。この方法では、覆い隠した名前のグリフ、ベクターの線、埋め込み画像など、すでにページ上に存在していたすべてのバイトデータは、ストリーム内の1つ前の命令としてそのまま残ります。準拠するビューアーは黒いボックスを最後に描画するため、視覚的には覆い隠されますが、その下にあるデータ自体は一切変更されません
この場合、誰でも3つの方法で元のコンテンツを復元できます。不透明なボックス越しにテキストを直接選択してコピーする、ページに対してテキスト抽出処理を実行する、またはコンテンツストリームから覆っている長方形を削除する、という方法です。MaskRectはその名の通り、単にマスク(覆い隠し)をするだけであり、削除は行いません。これは、ステッカーを剥がせば読めてしまう文字の上にステッカーを貼っているようなものです
RedactRegionが命令レベルでコンテンツを削除する仕組み
RedactRegion(Page, Left, Top, Width, Height, Options)は、描画層の1つ下のレベルで機能します。losLab PDF Libraryは、ページの各コンテンツレイヤーをTPDFContentProgramに解析し、CTM(電流変換行列)を追跡するアナライザー(コンテンツモデルのFindInstructionsInRect)を使用して命令リストをスキャンします。そして、指定された長方形領域と交差するすべてのテキスト表示、パス描画、および画像描画(Do)演算子をマークします。これらの命令はTPDFContentProgram.Deleteによって削除され、編集されたレイヤーはWritePageContentLayerによって書き戻されます。バイトデータはカバーの裏に隐藏されるのではなく、ストリームから完全に排除されます。そのため、後からテキスト抽出を実行しても何も検出されません
実装上の2つの詳細を把握しておくと役立ちます。RedactRegionは、現在の原点と単位に基づく左上の座標系で長方形を受け取り、それをコンテンツストリームが使用する左下のユーザー空間座標系に内部で変換します。そのため、開発者はページ上で見えている通りの領域を指定できます。長方形が複数の命令にまたがる場合、RedactRegionはインデックスの降順でそれらを削除します。インデックス7の前にインデックス4を削除してしまうと、以降のすべてのインデックスがずれて処理範囲が崩れてしまうからです。また、Options = 0を指定すると、losLab PDF Libraryは消去された領域の上に視覚的なマークとして黒塗りのボックスを重ねて描画します。ただし、このマークはあくまで意匠的なものであり、実際のコンテンツが削除された後に適用されます
実際には、長方形の座標をハードコーディングすることはほとんどありません。ページから機密文字列を検索してそのバウンディングボックスを取得し(これについてはDelphiでのテキストおよびページ要素の検索を参照してください)、その長方形の座標をRedactRegionに渡します
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('contract.pdf', '') = 1 then
begin
// Delete everything painted inside a 220 x 14 pt box on page 1.
// Coordinates are top-left, in the current origin and units.
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // Options=0 stamps a black marker
PDF.SaveToFile('contract-redacted.pdf');
end;
finally
PDF.Free;
end;
end;
墨消しされた情報はPDF内のどこに隠されていますか?
視覚的なレイヤーのみで墨消しを終わらせてしまうと、情報漏洩の痕跡が残ります。PDFは一般的な閲覧者が目にしない場所に情報を保存しているためです。losLab PDF Libraryは、これらの漏洩経路をいくつかのチャネルに分類しています。文書情報ディクショナリやXMPメタデータストリームには、本文から削除したはずの作成者、制作ツール、またはキーワードといった文字列が頻繁に含まれています。また、文書レベルのJavaScriptやカタログのOpenActionは、文書を開いたときに実行され、すでに消去したと思っていたデータにアクセスする可能性があります。さらに、すべての注釈にはオプションで/Aアクションを設定でき、注釈自体(付箋、古いフォームフィールド、リンクなど)が墨消しした領域の上に重ねて配置され、その内部にテキストのコピーを保持していることもあります。これらは暗号化の対象外です。暗号化されたファイルであっても平文のXMPパケットが含まれていると、検索エンジンなどのインデクサーにタイトルが漏洩してしまいます。これについては、個別に行うべき暗号化と権限の監査に関する関連記事を参照してください。適切なサニタイズを行うには、見えているページだけでなく、これらの非表示領域も漏れなく処理する必要があります
SanitizeDocumentによるドキュメントのサニタイズ
losLab PDF Libraryは、これらの多様な情報漏洩に対処するため、ビットマスクで指定された独立したクリーンアップ処理を実行し、適用された処理数を返すSanitizeDocument(Flags)を提供しています。これらのフラグは組み合わせて使用できます。SANITIZE_JAVASCRIPTは、すべての文書レベルのJavaScriptパッケージとOpenActionスクリプトを削除します。SANITIZE_ACTIONSは、新しいTPDFAnnots.RemoveAnnotActionを介してすべての注釈から/Aアクションディクショナリを削除し、カタログのOpenActionをクリアします。SANITIZE_METADATAは、文書情報ディクショナリ(ISO 32000-1 §14.3.3)およびXMPメタデータストリーム(§14.3.2)の両方において、作成者、件名、キーワード、作成アプリケーション、および制作ツールをクリアします。SANITIZE_ANNOTATIONSはドキュメント全体のすべての注釈を削除し、SANITIZE_OPEN_ACTIONはカタログの/OpenActionエントリを削除します。SANITIZE_ALLはこれら5つのフラグすべての論理和を示します
これらのクリーンアップ処理で扱われるインデックスの仕様は手動ループ処理を記述した際にバグを誘発しやすい部分であり、これこそがSanitizeDocumentが単一のメソッドとしてまとめられている最大の理由です。GlobalJavaScriptPackageNameは0ベースで機能し、RemoveGlobalJavaScriptは実行時にリストを縮小していくため、JavaScriptの削除処理は常にパッケージインデックス0を指し、カウントがゼロになるまで削除を繰り返します。一方、DeleteAnnotationは1ベースで動作します。また、AnnotationCountは現在選択されているページに対してカウントを報告するため、注釈の削除処理は順番に各ページを選択してから処理を行います。losLab PDF Libraryはこれらの境界条件を内部で適切に制御するため、開発者は5つの異なるループ規則を再現することなく、1つのフラグセットを渡して適用された処理数を受け取るだけで済みます
var
Applied: Integer;
begin
// Strip metadata, scripts, and actions but keep the annotations:
Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);
// Or clear every hidden channel, annotations included:
Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;
墨消しとサニタイズの完全なワークフロー
これら2つの処理を組み合わせることで、ワークフロー全体を非常にシンプルに構成できます。losLab PDF Libraryを使用して、ページごとにRedactRegionを実行して視覚的なコンテンツを削除し、ファイルを書き出す前にSanitizeDocument(SANITIZE_ALL)を実行してすべての非表示のチャネルを消去します。墨消しは、開発者が制御できない外部から提供されたドキュメントに対して実行されることがほとんどであるため、セキュアなインポートパスを介してロードすることをお勧めします。これについては、信頼できないPDFの安全な解析で解説されている防御策を参照してください。これにより、不正な形式の入力データがあった場合でも、墨消し処理の途中でクラッシュすることなく安全にエラーとして処理できます
var
PDF: TPDFlib;
Applied: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('incoming.pdf', '') = 1 then
begin
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // delete the sensitive line
Applied := PDF.SanitizeDocument(SANITIZE_ALL); // clear metadata, JS, actions, annots
PDF.SaveToFile('published.pdf');
end;
finally
PDF.Free;
end;
end;
コンテンツストリーム墨消しの限界
RedactRegionはコンテンツストリームに対して動作します。これは、デジタル生成されたテキストが存在する場所であり、スキャンされたテキストが存在する場所ではありません。ページがスキャンされた画像である場合、文字はテキスト演算子ではなく画像XObject内のピクセルデータとして存在するため、losLab PDF Libraryはグリフを削除するように画像データの一部から名前のみを切り取ることはできません。このライブラリの画像処理は意図的に厳格に設計されており、指定された長方形領域と少しでも交差する画像オブジェクトは全体が削除されます。そのため、スキャンされたページ上で墨消しを実行すると、画像の一部分だけでなく画像全体が消去されます。画像内の一部の領域を墨消しするには、ピクセルレベルでのラスタライズ、描画、および再埋め込みが必要となり、これはコンテンツストリームの削除とは異なる処理になります。同様の注意が、スキャンされた画像の上に重ねられたOCRテキストレイヤーにも当てはまります。RedactRegionは検出可能なテキスト演算子を削除しますが、下部にある画像データ自体は、その画像XObjectが長方形領域と交差しない限り変更されません
処理結果の整合性を保つための2つの注意点があります。交差判定は、厳密なグリフ寸法を測定するのではなく、文字数とフォントサイズから各テキストランの幅を概算します。そのため、プロポーショナルフォントで書かれた非常に短い単語などは、狭く指定した長方形領域から外れてしまうことがあります。長方形の指定には少しマージンを持たせることをお勧めします。また、削除は命令単位で実行されます。RedactRegionはテキスト表示演算子全体を削除するため、単語の一部分のみを切り取ろうとした場合でも、同じ演算子を共有する隣接する文字も一緒に削除されてしまいます。これらの制限はメカニズムの信頼性を損なうものではなく、長方形の領域を慎重に定義し、処理結果を個別に検証することの重要性を示しています
墨消しワークフローを完了させる鉄則は、推測ではなく検証を行うことです。領域を墨消しし、ドキュメントをサニタイズし、新しいファイルに保存し、それを再起動して削除したテキストとクリアしたメタデータの両方の復元を試みてください。どちらも復元できなければ、墨消しは成功しています。ここで紹介したRedactRegionおよびSanitizeDocumentのAPIは、詳細なコンテンツモデルおよびサニタイズのリファレンスとともに、DelphiおよびC++Builder向けのlosLab PDF Libraryに含まれています