コンテンツにスキップ

PDF を暗号化して権限を制限する

このレシピでは、AES-256 標準セキュリティハンドラーでドキュメントを暗号化します。ユーザーパスワード(開くために必要)とオーナーパスワード(フルアクセス用)を設定し、権限ビットマスクで操作を制限します。このレシピでは、これらの権限が リーダー協調型 である点を意図的に明確にしています。暗号化が提供するのは 機密性 であり、完全性ではありません。また、権限ビットは協調するソフトウェアによってのみ尊重されます。このレシピは examples/22-protection.php に沿っています。

信頼境界(権限に関するすべての主張に必ず付記してください)。 PDF 暗号化はパスワードを持たない者に対してコンテンツの 機密性 を保護します (ISO 32000-2 §7.6)。 これは 完全性保護しません — 変更の検出も防止も行いません。権限 P エントリは符号なし 32 ビットのフラグ集合であり、 準拠リーダーに尊重が求められるものです。これらは アクセス制御ではありません。非準拠のツール、またはオーナーパスワードで使用される任意のツールは、 あらゆる「拒否された」操作を実行できます。暗号化された PDF を 「セキュア」「改ざん不可能」「コピー保護済み」と表現しないでください。

Terminal window
composer require nextpdf/core:^3

PHP の openssl 拡張を有効にしてください。AES-256 暗号化処理は、暗号化と鍵導出にこれを使用します。

標準セキュリティハンドラーは、暗号化辞書の V/R コードによって選択されます(ISO 32000-2 §7.6)。NextPDF の Aes256Encryptor は、セキュリティハンドラー リビジョン 6V=5/R=6)で AESV3 暗号フィルターを実装します。ランダムな 256 ビットのファイル暗号化鍵、ソルト付き反復ハッシュによる鍵導出(Algorithm 2.B)、およびランダムな初期化ベクトルを用いた、オブジェクト単位の AES-256-CBC 暗号化を行います。CBC は機密性モードです(NIST SP 800-38A)。その IV は予測不可能でなければなりません。

IV はオブジェクトごと・実行ごとに新規生成されるため、生のバイト列は実行ごとに異なります。したがって再現性プロファイルは structural です。2 つの実行を比較する前に、ハーネスは暗号化 IV、オブジェクト順序、およびトレーラーの /ID を正規化します。このプロファイルは、暗号化を省略するレシピのプロファイルよりも厳格です。

権限ビットマスクは P エントリを設定します。ビット 3 は印刷を、ビット 6 は annotation/form-fill を許可します。値は、文書化された符号なし 32 ビット量です。

NextPDF\Core\Concerns\HasSecurityDocument にミックスイン):

  • setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static — AES-256 標準ハンドラー暗号化を構成します。permissions = -1 はすべてを許可します。ownerPassword が空の場合、ユーザーパスワードがオーナーパスワードとして再利用されます。addPage() の前に呼び出してください。
  • getEncryptor(): ?Aes256Encryptor — 構成済みの暗号化処理、または null
  • useAesGcm(?bool $enabled = true): static — ISO/TS 32003 AES-256-GCM の利用をオプトインします。ホストの OpenSSL/libsodium に暗号がない場合は例外をスローします。

両方のパスワードパラメーターは #[SensitiveParameter] でマークされているため、PHP はそれらをスタックトレースから秘匿します。

権限ビット(P エントリ。一般には下位のビット 3〜6):

ビット操作
34ドキュメントを印刷
48ドキュメントの内容を変更
516テキストとグラフィックをコピー/抽出
632注釈の追加・変更、およびフォームフィールドの入力
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Confidential Memo');
// Grant printing only (bit 3 = 4). MUST run before addPage().
$doc->setEncryption(
userPassword: 'open-me',
ownerPassword: 'owner-secret',
permissions: 4,
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'Encrypted with AES-256; printing allowed only.', newLine: true);
$doc->save(__DIR__ . '/confidential.pdf');
echo "Wrote confidential.pdf\n";

以下の完全な例は examples/22-protection.php を反映しており、ハーネス向けに NEXTPDF_COOKBOOK_OUTPUT へ書き込みます。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$userPassword = 'demo';
$ownerPassword = 'admin';
// Grant ONLY printing (bit 3 = 4); deny copy/modify/annotate.
$permissions = 4;
$doc = Document::createStandalone();
$doc->setTitle('Encrypted Document — Restricted Permissions');
$doc->setAuthor('NextPDF Example');
// setEncryption() MUST be called before addPage().
$doc->setEncryption(
userPassword: $userPassword,
ownerPassword: $ownerPassword,
permissions: $permissions,
);
$doc->addPage();
$doc->setFont('helvetica', 'B', 20);
$doc->cell(0, 14, 'Encrypted PDF Document', newLine: true);
$doc->ln(8);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'This document is protected with AES-256 encryption '
. '(standard security handler, revision 6). The user password is required '
. 'to open it; the owner password grants full access. The permission '
. 'bits below are honoured by conforming readers only.');
$doc->ln(5);
$permissionTable = [
['Bit 3 (4)', 'Printing', 'ALLOWED'],
['Bit 4 (8)', 'Content modification', 'DENIED'],
['Bit 5 (16)', 'Text copying / extraction', 'DENIED'],
['Bit 6 (32)', 'Annotations / form fields', 'DENIED'],
];
$doc->setFont('helvetica', 'B', 10);
$doc->cell(30, 7, 'Flag');
$doc->cell(60, 7, 'Operation');
$doc->cell(0, 7, 'Status', newLine: true);
foreach ($permissionTable as [$bit, $operation, $status]) {
$doc->setFont('courier', '', 9);
$doc->cell(30, 7, $bit);
$doc->setFont('helvetica', '', 10);
$doc->cell(60, 7, $operation);
$doc->setFont('helvetica', 'B', 10);
$doc->cell(0, 7, $status, newLine: true);
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/encrypted.pdf');
echo "Wrote encrypted PDF (AES-256, printing only)\n";

想定される出力:

Wrote encrypted PDF (AES-256, printing only)

ファイルを開くと、パスワードの入力を求められます。ユーザーパスワードでは、制限された権限セットのもとでファイルを開きます。オーナーパスワードでは、フルアクセスでファイルを開きます。

  • 呼び出し順序。 setEncryption()addPage() の後に呼び出しても、それ以前のコンテンツが遡って暗号化されることはありません。常に暗号化を最初に構成してください。エンジンは各オブジェクト本体を書き込む際に暗号化します。
  • オーナーパスワードのデフォルト。 オーナーパスワードが空の場合、エンジンはユーザーパスワードをオーナーパスワードとして再利用します。その場合、特権ロールは実質的に存在しなくなります。2 つのロールを区別する必要がある場合は、異なるパスワードを設定してください。
  • 権限のセマンティクスはアドバイザリーです。 ビットは準拠リーダーによってのみ尊重されます。これらは暗号的に強制されません。非準拠のツール、またはオーナーパスワードで使用される任意のツールは、制限された操作を実行できます。権限は協調するソフトウェアへのポリシーシグナルとして扱い、意図的に回避しようとする相手に耐えるアクセス制御として扱わないでください。
  • 完全性の保証はありません。 暗号化は機密性を提供するものであり、完全性を提供するものではありません。パスワードを持たない攻撃者はコンテンツを読み取れませんが、フォーマット自体は改ざんを検出しません。完全性保護には、別個のメカニズム(デジタル署名、または ISO/TS 32004 のドキュメント MAC)が必要です。
  • PDF/A との競合。 PDF/A は Encrypt トレーラーキーを禁止します。PDF/A ドキュメントに対して setEncryption() を呼び出すと、順序にかかわらず非互換例外がスローされます。
  • AES-256-GCM のオプトイン。 useAesGcm() は、ホストの OpenSSL または libsodium が提供する場合に ISO/TS 32003 GCM バルク暗号化を選択します。提供されない場合は InvalidConfigException をスローします。同じ理由で、これは PDF/A と非互換です。
  • 公開鍵暗号化はまだ配線されていません。 setPublicKeyEncryption() は API サーフェスを固定しますが、ライターの配線が実装されるまで save() はスローします(既知の不具合)。Core の本番環境では使用しないでください。

鍵導出では、ドキュメントごとに 1 回、Algorithm 2.B の反復ハッシュを実行します。オブジェクト単位の AES-256-CBC は、オブジェクト本体のサイズに対して線形にスケールします。一般的なドキュメントでは、コストは 1500 ms/64 MB の予算内に十分収まります。非常に大きなドキュメントでは、オブジェクトごとに AES のスループットコストが発生します。AES-NI を備えたホストでは、GCM のほうが高速です。

  • 機密性のみ。 信頼境界を改めて示します。暗号化はパスワードを持たない者からコンテンツを守りますが、ファイルが改変されていないことを証明するものではなく、権限ビットはリーダー協調型です。
  • パスワードの強度はあなた次第です。 ハンドラーの強度はパスワードの強度を超えません。弱いユーザーパスワードは、ファイルが入手された時点でオフラインの総当たり攻撃にさらされます。フォーマットは試行をレート制限できません。
  • オーナーパスワードはプライマリキーです。 オーナーパスワードを持つ者は、あらゆる制限を回避します。ルート資格情報のように扱ってください。ドキュメントとともに配布したり、ログに記録したりしないでください。
  • #[SensitiveParameter] は多層防御です。 これは PHP のスタックトレースからパスワードを秘匿しますが、それでもあなた自身のログ、例外メッセージ、クラッシュレポートから除外しておく必要があります。

ライブラリは暗号化をプロセス内で実行します。ドキュメントやパスワードをどこにも送信しません。保存する暗号化済み出力を除き、エンジンがパスワード、鍵、ドキュメントのバイト列をディスクに書き込むことはありません。出力ファイルの保存場所とパスワードの保管は、インテグレーターが担うデプロイメント上の関心事です。ライブラリはレジデンシーを保証しません。平文ドキュメントに個人データが含まれる場合、そのデータは最も弱いパスワード、および上記のリーダー協調に関する注意点と同程度にしか保護されません。暗号化は、ドキュメントに含める PII を最小化することの代替にはなりません。

暗号化は EncryptionAppliedEvent を発行します。これはアルゴリズム名(AES-256)と、print/copy/modify が許可されているかどうかを要約する 3 つのブール値だけを保持します。パスワード、鍵、ソルト、IV がイベントに置かれることは決してありませんsrc/Event/Security/EncryptionAppliedEvent.php)。OpenTelemetry のパスでは、スパン属性を許可リスト方式のサニタイザー(src/Telemetry/AttributeSanitizer.php)を通じてルーティングします。サニタイザーはパスワードとファイルパスを無条件に拒否し、スカラー値を持つ許可リスト済みのキーだけを残します。あなた自身の統合コードでは、スパン、ログ、例外メッセージにパスワードや鍵素材を追加しないでください。#[SensitiveParameter] マーカーはスタックトレースを保護しますが、あなた自身が組み立てた文字列は保護しません。

スコープ内:暗号化されたファイルを入手したが、パスワードは持たない攻撃者。攻撃者はコンテンツを読み取れず(パスワードの強度に依存)、ファイルは平文を漏らしません。スコープ外:ユーザーまたはオーナーパスワードを持つ攻撃者、権限ビットを無視する非準拠リーダー、弱いパスワードに対するオフラインの総当たり攻撃、改ざん検出(暗号化が提供するのは機密性であって完全性ではありません)、ホストの OpenSSL ビルドのサイドチャネル、そして鍵の保管。鍵の保管はすべてインテグレーターの責任です。これらの脅威を文書化することは、脆弱性が存在しないことを主張するものではありません。

暗号プリミティブはホストの OpenSSL ビルドによって提供されるため、FIPS の状態はライブラリ設定ではなくホストのプロパティです。CryptoCapabilities::detectFipsMode() は 3 状態の FipsModeDetectionsrc/Security/FipsModeDetection.php)を返します。値は FIPS_ACTIVEFIPS_ABSENT、または INDETERMINATE です。PHP の openssl 拡張は OpenSSL 3 のプロバイダーモデルに対するバインディングを公開しないため、プローブはベストエフォートです。INDETERMINATE は「FIPS は証明されていない」(フェイルクローズ)として扱われ、オペレーターが対応できるテレメトリで区別できます。NextPDF は FIPS 140 検証を主張しません。FIPS 検証済みの OpenSSL 上で実行することはオペレーターの責任であり、検出結果はアドバイザリーです。

ステートメント仕様条項リファレンス ID
暗号化辞書の V コードが暗号化アルゴリズムを選択します。ISO 32000-2§7.6
AESV3 暗号フィルターメソッドは CFM エントリで名付けられます。ISO 32000-2§7.6
この P エントリは符号なし 32 ビットのアクセス権限量です。ISO 32000-2§7.6
権限ビット 3 は印刷を制御します。ISO 32000-2§7.6
権限ビット 6 は注釈/フォーム入力を制御します。ISO 32000-2§7.6
暗号化はコンテンツを不正アクセスから保護します(機密性)。ISO 32000-2§7.6
リビジョン 6 の鍵導出は、ソルト付き反復ハッシュ(Algorithm 2.B)を使用します。ISO 32000-2§7.6
CBC は機密性モードです(完全性モードではありません)。NIST SP 800-38A§6.2
CBC の初期化ベクトルは予測不可能でなければなりません。NIST SP 800-38A附属書 C

NextPDF は引用された条項を実装します。これは、包括的な ISO 32000-2 適合性、FIPS 140 検証、または機密性に関するいかなる法的・契約的保証も 主張しません。「標準セキュリティハンドラーのサポート」は、あなたのデプロイメントにおけるセキュリティ認証ではありません。それはライブラリの外部にあるパスワードの保管と検証ポリシーに依存します。