コンテンツにスキップ

暗号化: AES-256 (CBC) および AES-256-GCM

Core は、ISO 32000-2:2020 §7.6 の標準セキュリティハンドラーに基づき、PDF を AES-256 で暗号化します。デフォルトモードは V=5 / R=6 / AESV3 (AES-256-CBC) です。オプトインモードは ISO/TS 32003:2023 V=6 / R=7 の AES-256-GCM 認証パスです。このページでは、鍵導出、ワイヤーフォーマット、許可境界、デプロイ時に理解しておくべき制限について説明します。

Terminal window
composer require nextpdf/core:^3

デフォルトパスには openssl 拡張モジュールが必要です。AES-256-GCM パスは openssl または ext-sodium を使用します。AES-NI ハードウェアを持たないホストでは libsodium が GCM を拒否するため、Core はアルゴリズムを劣化させるのではなく、より低速な OpenSSL 実装にフォールバックします。

デフォルトのハンドラーは、AESV3 暗号フィルターを備えた V=5 / R=6 の標準セキュリティハンドラーです。setEncryption() の呼び出し時に、Core はプラットフォームの暗号論的乱数ソース(random_bytes())からランダムな 256 ビットのファイル鍵を生成します。バイト長は 32 バイトで、FIPS 197 の鍵長と一致します。オブジェクトごとのコンテンツは AES-256-CBC で暗号化されます。ISO 32000-2:2020 §7.6.4 が定めるとおり、16 バイトの初期化ベクトルが各暗号文の先頭に付加されます。

鍵導出はリビジョン 6 のアルゴリズム 2.B に従います。パスワードはまず SASLprep (RFC 4013) で正規化され、その後 ISO 32000-2:2020 §7.6.4.3.3 が定めるとおり、文字境界で 127 UTF-8 バイトに切り詰められます。導出されるハッシュは、AES-128-CBC ステップによって駆動される反復的な SHA-256 / SHA-384 / SHA-512 ルーチンで計算され、これによりオフラインでのパスワード推測のコストが引き上げられます。ユーザー、オーナー、鍵ごとのソルトは暗号化インスタンスごとに一度だけ生成されるため、単一のインスタンスは決定論的な辞書バイトを出力します。これはマルチパスライターの前提条件です。

useAesGcm() は、オプトインの AES-256-GCM パスを有効にします。これは ISO/TS 32003:2023 V=6 / R=7 の AESV4 暗号フィルターを実装します。暗号方式は、NIST SP 800-38D のパラメーターを用いた AES-256-GCM です。暗号化オブジェクトごとのワイヤーレイアウトは、12 バイトの IV、暗号文、続く 16 バイトの認証タグです。TS 32003 §5.2 プロファイルが定めるとおり、追加認証データは空です。復号時にはタグを検証し、不一致の場合は TamperedDataException を発生させます。タグの検証に失敗した場合に平文を返すことはありません。このパスは、デフォルトの CBC パスだけでは提供されない改ざん検出を追加します。

GCM パスにおける IV の一意性の規律は NIST SP 800-38D §8 に従います。IV の上位 4 バイトは、構築時にランダムソースから設定される、インスタンスごとの固定フィールドです。下位 8 バイトは、発行された IV ごとにインクリメントされるビッグエンディアンのカウンターです。これは §8.2.1 の決定論的構築手法と一致しますが、固定フィールドはドキュメント間の衝突を防ぐため、列挙ではなくランダム化される点が異なります。第 2 のガードは、発行されたすべての IV を衝突セットに記録し、値が重複した場合は NonceReuseException を発生させます。カウンターのロールオーバーも NonceReuseException を発生させます。ロールオーバーは、§8 が警告する IV 再利用の障害モードであるためです。

GCM パスには 2 つの長さの上限が適用されます。オブジェクトごとの平文の上限は 2^39 − 256 バイトで、これは NIST SP 800-38D §5.2.1.1 から導かれる呼び出しごとの上限です。これより大きい入力は、オブジェクト全体に分割するよう案内する長さ例外を発生させます。呼び出しの安全上限は、鍵ごとに 2^32 回の呼び出しです。assertWithinSafetyBound() はオプトインのチェックで、GcmInvocationLimitExceededException を発生させることで、呼び出し側が §8.3 のしきい値に達する前にドキュメント鍵をローテーションできるようにします。NIST SP 800-57 Part 1 §4 は、この鍵のライフタイムに関する判断をデプロイ側の責任として位置付けています。

許可フラグは助言的なものです。ビットマスクは暗号化された /Perms エントリと /P 値に書き込まれ、読み取り時に validatePerms() で復元されます。これは破損したマーカーに対してフェイルクローズします。準拠リーダーはフラグを尊重することが期待されます。フラグは暗号によって強制されません。復号鍵を持ち、ビットを無視するプロセッサーは、コンテンツの読み取り、コピー、変更が可能です。許可フラグは、アクセス制御ではなく、リーダーの慣習として説明してください。

種別主なメンバー安定性導入バージョン
Aes256Encryptorclassencrypt(), decrypt(), encryptForObject(), buildEncryptionDictionary(), verifyUserPassword(), verifyOwnerPassword(), validatePerms(), getEncryptionKey()安定版1.0.0
Aes256GcmEncryptorclassencrypt(), decrypt(), encryptStream(), assertWithinSafetyBound(), invocationCount(), isAvailable()安定版2.18.0
KeyMaterialfinal readonly classgenerate(), exposeKey(), fingerprint()安定版2.18.0
EncryptedPayloadSpecfinal readonly classtoDict()安定版2.18.0
CryptoCapabilitiesfinal classhasAesGcm(), detectFipsMode(), assertFipsAvailableForProfile()安定版2.0.0
NonceReuseException例外安定版2.18.0
TamperedDataException例外安定版2.18.0
DecryptionFailedException例外安定版2.18.0
GcmInvocationLimitExceededException例外安定版3.0.0
examples/22-protection.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// AES-256-CBC, V=5/R=6. Call before addPage().
$doc->setEncryption(
userPassword: 'demo',
ownerPassword: 'admin',
permissions: 4, // printing only; copy/modify denied for a conforming reader
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 8, 'Confidential', newLine: true);
$doc->save(__DIR__ . '/output/22-protection.pdf');
examples/security/gcm-authenticated-encryption.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Security\CryptoCapabilities;
use NextPDF\Security\Encryption\Aes256GcmEncryptor;
use NextPDF\Security\Exception\TamperedDataException;
use NextPDF\Security\KeyMaterial;
use Psr\Log\LoggerInterface;
final readonly class AuthenticatedBlobCipher
{
public function __construct(private LoggerInterface $logger) {}
/**
* Seal a payload with AES-256-GCM and return the wire-format bytes.
*
* @param non-empty-string $plaintext The payload to protect.
*
* @return non-empty-string IV(12) || ciphertext || tag(16).
*/
public function seal(string $plaintext, KeyMaterial $key): string
{
if (!CryptoCapabilities::hasAesGcm()) {
throw new \RuntimeException('Host cannot perform AES-256-GCM.');
}
$cipher = new Aes256GcmEncryptor($key);
// Opt-in NIST SP 800-38D §8.3 key-rotation guard.
$cipher->assertWithinSafetyBound();
$wire = $cipher->encrypt($plaintext);
$this->logger->info('Payload sealed', [
'key_fingerprint' => $key->fingerprint(),
'invocations' => $cipher->invocationCount(),
]);
return $wire;
}
/**
* Open a sealed payload; a modified payload raises, never returns plaintext.
*
* @param non-empty-string $wire IV(12) || ciphertext || tag(16).
*/
public function open(string $wire, KeyMaterial $key): string
{
try {
return (new Aes256GcmEncryptor($key))->decrypt($wire);
} catch (TamperedDataException $e) {
$this->logger->warning('Tampered payload rejected', [
'key_fingerprint' => $key->fingerprint(),
]);
throw $e;
}
}
}

この暗号処理は、ホストの能力を確認し、オプトインの呼び出しガードを適用し、不可逆な鍵フィンガープリントのみをログに記録します。また、疑わしいバイトを返すのではなく、改ざん拒否を再スローします。

  • デフォルトの AES-256-CBC パスは機密性のみを提供します。それ自体では、変更された暗号文を検出しません。改ざん検出が必要な場合は AES-256-GCM パスを使用してください。
  • useAesGcm() は、PDF/A モードが有効な場合、および opensslext-sodium のいずれも AES-256-GCM を提供しない場合に例外を発生させます。両方を捕捉し、オペレーターが対処可能なメッセージを提示してください。
  • AES-NI を持たないホストでは、libsodium が GCM を拒否します。Core は OpenSSL GCM にフォールバックします。これは正しいものの低速です。スループットは低下しますが、セキュリティは低下しません。
  • GCM のオブジェクトごとの平文の上限は 2^39 − 256 バイトです。これより大きい入力は長さ例外を発生させます。encryptStream() でコンテンツを複数のオブジェクトに分割してください。
  • 渡される KeyMaterial インスタンスは正確に 32 バイトでなければなりません。長さが誤っている場合は、切り詰められるのではなく、構築時に拒否されます。
  • リーダーパス(verifyUserPassword()verifyOwnerPassword()validatePerms())は、暗号論的素材に対して定数時間比較を使用し、破損した許可マーカーに対してフェイルクローズします。

オブジェクトごとの AES-256-CBC 暗号化は 1 回の OpenSSL 呼び出しであり、オブジェクト本体に対して O(n) です。鍵導出では、反復的なアルゴリズム 2.B ルーチンを暗号化インスタンスごとに一度実行します。コストは有界で、ドキュメントごとに一定です。AES-256-GCM のストリーミングパスは入力を 16 MiB のチャンクに分割します。これにより、合計入力サイズに関係なくライブヒープが約 64 MB に制限され、ドキュメント化された 64 MB のピーク予算を十分に下回ります。各 GCM オブジェクトは 28 バイトのオーバーヘッド (12 バイトの IV と 16 バイトのタグ) を追加します。AES-NI ハードウェアは GCM のスループットを大幅に向上させます。AES-NI がない場合は、スループットが低下するだけです。

このサーフェスの脅威モデルは明示的です。オフラインでのパスワード推測のコストは、SASLprep 正規化と反復的なリビジョン 6 の鍵導出によって引き上げられますが、弱いパスワードは依然として支配的な残存リスクです。どのような導出も、そのリスクを取り除くことはできません。暗号文の変更は、タグ検証を通じて GCM パスでは検出されますが、デフォルトの CBC パスでは検出されません。GCM パスでの IV の再利用は、カウンターと衝突セットによって防止され、NIST SP 800-38D §8.1 の IV 規律と整合します。カウンターのロールオーバーはラップせずに拒否します。ログを介した鍵の漏洩は、KeyMaterial のリダクションと、パスワードに対する #[\SensitiveParameter] 属性によって緩和されます。導出された鍵素材は、プラットフォームが許す場合、使用後にゼロクリアされます。

境界も同様に明示的です。AES-256 暗号化は、ISO 32000-2:2020 §7.6、およびオプトインパスについては ISO/TS 32003:2023 §5.2 で定義されたとおりに適用されます。実効的な保護は、パスワードの強度、鍵管理、デプロイ環境、利用するリーダーに依存します。許可フラグは準拠リーダーによって尊重されますが、暗号によって強制されることはありません。/Perms 値に使用される AES-ECB ステップは、単一の 16 バイトブロックに対して ISO 32000-2:2020 §7.6.4.4.10 によって義務付けられています。これは汎用モードではありません。2^32 回の呼び出し上限に達する前の鍵のローテーションは、Core がチェック手段を公開しているもののデフォルトでは強制しない、デプロイ側の責任です。

暗号化と復号はプロセス内で実行されます。このサーフェスを通じて、ドキュメントのバイト、パスワード、鍵の値がホストの外に出ることはありません。GCM の IV 衝突セットは、鍵のバイトではなく、不可逆な鍵フィンガープリントをキーにします。鍵を外部の鍵管理システムまたは PKCS#11 トークンで前置するデプロイは、そのバックエンドのレジデンシーについて責任を負います。OASIS PKCS#11 v3.1 の C_GenerateKey が、トークン常駐の鍵生成の契約点です。

ポリシー名と 8 文字の鍵フィンガープリントをログに記録し、鍵やパスワードは決して記録しないでください。KeyMaterial::__toString()__debugInfo() はリダクトされたプレースホルダーを返します。このサーフェスからの例外メッセージは、鍵のバイトではなく、操作ラベルとフィンガープリントを保持します。GCM の呼び出し回数は、鍵ローテーションのダッシュボードにとって安全なテレメトリ信号です。

脅威Core での緩和策残存境界
オフラインでのパスワード推測SASLprep と反復的なリビジョン 6 の導出弱いパスワードは依然として支配的なリスク
暗号文の変更GCM タグ検証 (オプトインパス)CBC パスは機密性のみ
IV の再利用 (GCM)ランダムな固定フィールド、カウンター、衝突セット。ロールオーバーは拒否
過大な GCM 平文長さチェック (2^39 − 256)。分割の案内呼び出し側が大きな入力をストリーミングする必要がある
鍵の過剰使用 (GCM)assertWithinSafetyBound() による上限 2^32オプトイン。デフォルトでは強制されない
許可フラグのバイパスなし — フラグは助言的非準拠のリーダーはフラグを無視する
ログを介した鍵の漏洩KeyMaterial のリダクション。#[\SensitiveParameter]ログに exposeKey() を記録する呼び出し側はこれを無効化する

Core は FIPS 検証済みの暗号モジュールではなく、FIPS 認証も受けていません。CryptoCapabilities::detectFipsMode() は、有効、不在、不確定を報告するベストエフォートのプローブであり、assertFipsAvailableForProfile() は、FIPS プロバイダーを証明できないホストで FIPS プロファイルが選択された場合にフェイルクローズします。暗号化サーフェスは、FIPS 検証済みのプロバイダーを読み込んだホストの OpenSSL ビルドに対して実行される場合、FIPS 互換モードで動作します。検証済みかつ認証済みの体制は Enterprise の領域です。

主張標準条項根拠
すべての GCM IV は、決定論的な固定フィールドとカウンターの構築により、呼び出しごとに一意です。NIST SP 800-38D(GCM モード規格)§8.2.1
IV 構築の規律により、1 つの鍵での呼び出し間の再利用が防止されます。NIST SP 800-38D(GCM モード規格)§8.1
オブジェクトごとの平文の上限は、呼び出しごとの長さの上限と一致します。NIST SP 800-38D(GCM モード規格)§5.2.1.1
鍵の暗号利用期間とローテーションはデプロイ側の責任です。NIST SP 800-57 Part 1 Rev. 5(鍵管理ガイドライン)§4
AES ファイル鍵は 256 ビットで、標準の鍵長と一致します。FIPS 197§4.2.1
トークン常駐の鍵生成は、外部鍵ストアの統合ポイントです。OASIS PKCS#11 v3.1(暗号トークン規格)C_GenerateKey(鍵生成の呼び出し)

ISO 32000-2:2020 §7.6 および ISO/TS 32003:2023 §5.2 は、ここでドキュメント化されているハンドラーの規範的基盤です。これらのテキストはライセンスによって制限されています。このページはそれらを言い換え、条項を番号で引用し、いずれの内容も引用していません。バイト単位で正確な鍵導出に対する検証済みのランタイム根拠は、アルゴリズム 2.B 標準テストと、ページの根拠トレーラーに記載された外部オラクルフィクスチャです。

Core は、デフォルトの AES-256-CBC パスとオプトインの AES-256-GCM パスの両方を、ローカル鍵サーフェスと暗号ポリシーゲートとともに提供します。Enterprise エディションは、同じ契約のもとで HSM/PKCS#11 の鍵管理バックエンドと FIPS モードの暗号ポリシープロファイルを追加します。パブリック API は同一であり、鍵管理バックエンドとポリシー実装が異なります。