Skip to content

高度な暗号化

Pro — Commercial License Required
高度な暗号化の内部実装にはProパッケージが必要です。

このページでは、TCPDF-Next Proの内部暗号化実装について説明します。AES-256 AESV3ハンドラ、鍵導出アルゴリズム、パスワード正規化、およびセキュアなパラメータハンドリングをカバーしています。基本的な暗号化の使い方については、AES-256暗号化の例をご覧ください。

AESV3ハンドラによるAES-256

TCPDF-Next Proは、すべてのストリームおよび文字列暗号化にAES-256-CBCを義務付けるISO 32000-2(PDF 2.0)標準セキュリティハンドラリビジョン6を実装しています。ハンドラは暗号化ディクショナリの/V 5および/R 6で識別されます。

php
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;

$encryptor = new Aes256Encryptor(
    ownerPassword: 'Str0ng!OwnerP@ss',
    userPassword:  'reader2026',
);

RC4やAES-128がない理由

TCPDF-Next Proは、RC4(40ビットおよび128ビット)とAES-128を意図的に除外しています:

アルゴリズム除外理由
RC4-401995年以降に破られた、容易に攻撃可能
RC4-128キーストリームにバイアスあり、PDF 2.0で禁止
AES-128リビジョン6でAES-256に取って代わられた、前方互換性なし

PDF 2.0(ISO 32000-2:2020)は新規ドキュメントにAESV3を要求しています。弱いアルゴリズムのサポートはセキュリティ態勢を損ない、仕様に違反します。

アルゴリズム2.B:鍵導出

ファイル暗号化キーは、アルゴリズム2.B(ISO 32000-2、条項7.6.4.3.4)を使用してパスワードから導出されます。これはSHA-256、SHA-384、SHA-512をチェーンする反復プロセスです:

function computeHash(password, salt, userKey = ''):
    K = SHA-256(password || salt || userKey)

    round = 0
    lastByte = 0

    while round < 64 OR lastByte > round - 32:
        K1 = (password || K || userKey) repeated 64 times
        E  = AES-128-CBC(key = K[0..15], iv = K[16..31], data = K1)

        mod3 = (sum of all bytes in E) mod 3
        if   mod3 == 0: K = SHA-256(E)
        elif mod3 == 1: K = SHA-384(E)
        else:           K = SHA-512(E)

        lastByte = E[len(E) - 1]
        round += 1

    return K[0..31]   // 32バイトのファイル暗号化キー

この反復ハッシュにより、正当な使用には十分な速度を維持しながら、ブルートフォース攻撃を計算コスト的に困難にします。

暗号化ディクショナリのキーコンポーネント

エントリ長さ用途
/O48バイトオーナーパスワード検証(ハッシュ + 検証ソルト)
/U48バイトユーザーパスワード検証(ハッシュ + 検証ソルト)
/OE32バイトオーナー暗号化ファイル暗号化キー
/UE32バイトユーザー暗号化ファイル暗号化キー
/Perms16バイトAES-256暗号化された権限フラグ

SASLprepパスワード正規化

暗号操作の前に、パスワードはSASLprep(RFC 4013)を使用して正規化されます。これはstringprep(RFC 3454)のプロファイルです。オペレーティングシステムや入力方式で使用されるUnicode正規化形式に関係なく、一貫したパスワード処理を保証します。

php
use Yeeefang\TcpdfNext\Pro\Security\SaslPrep;

$normalized = SaslPrep::prepare('P\u{00E4}ssw\u{00F6}rd');
// 合成/分解形式を正規化し、特定の文字をマッピングし、
// 禁止コードポイントを拒否します。

SASLprepの機能

  1. マッピング -- 一般的にnothingにマッピングされる文字(例:ソフトハイフンU+00AD)が除去されます。
  2. 正規化 -- 文字列がUnicode NFC(正規分解後の正規合成)に変換されます。
  3. 禁止 -- RFC 3454のテーブルC.1.2からC.9の文字(制御文字、私用文字、サロゲート、非文字など)が拒否されます。
  4. 双方向チェック -- 左から右と右から左の両方の文字を含む文字列がRFC 3454条項6に従って検証されます。

これにより、U+00FC(LATIN SMALL LETTER U WITH DIAERESIS)を入力するユーザーとU+0075 U+0308(LATIN SMALL LETTER U + COMBINING DIAERESIS)を入力するユーザーが同じ暗号化キーを生成します。

権限エンコーディング

権限は/Permsエントリに16バイトのAES-256-ECB暗号化ブロックとして格納されます。平文のレイアウト:

Bytes 0-3:   権限フラグ(リトルエンディアンint32)
Bytes 4-7:   0xFFFFFFFF
Byte  8:     EncryptMetadataがtrueなら'T'、それ以外は'F'
Bytes 9-11:  'adb'
Bytes 12-15: ランダムパディング

権限フラグはISO 32000-2 テーブル22で定義されたものと同じビットレイアウトに従います。

暗号化ストリームの処理

PDFのすべてのコンテンツストリームと文字列は個別に暗号化されます:

  1. ストリーム/文字列ごとに一意の16バイト初期化ベクトル(IV)random_bytes(16)を使用して生成されます。
  2. IVは暗号文の先頭に付加されます。
  3. 暗号化前にPKCS#7パディングが適用されます。
  4. すべてのストリームデコードパラメータに/AESV3付きの/Cryptフィルタが設定されます。
php
// 内部処理 -- ライターによって自動的に処理されます
$iv        = random_bytes(16);
$padded    = pkcs7_pad($plaintext, blockSize: 16);
$encrypted = openssl_encrypt($padded, 'aes-256-cbc', $fileKey, OPENSSL_RAW_DATA, $iv);
$output    = $iv . $encrypted;

WARNING

TCPDF-Next Proは常にストリームデータを暗号化します。/EncryptMetadataフラグはデフォルトでtrueです。falseに設定すると、XMPメタデータストリームは暗号化されません(検索インデックス作成に有用)が、他のすべてのストリームは暗号化されます。

センシティブパラメータの処理

パスワードを受け取るすべてのメソッドには、PHP 8.2の#[\SensitiveParameter]属性が付加されています。これにより、パスワードがスタックトレース、デバッグ出力、エラーログに表示されることを防ぎます:

php
public function setOwnerPassword(
    #[\SensitiveParameter] string $password,
): self {
    $this->ownerPassword = SaslPrep::prepare($password);
    return $this;
}

例外が発生した場合、スタックトレースには実際のパスワード文字列ではなくObject(SensitiveParameterValue)が表示されます。

完全なサンプル

php
use Yeeefang\TcpdfNext\Core\Document;
use Yeeefang\TcpdfNext\Pro\Security\Aes256Encryptor;
use Yeeefang\TcpdfNext\Pro\Security\Permissions;

$pdf = Document::create()
    ->setTitle('Confidential Report')
    ->addPage()
    ->setFont('Helvetica', size: 12)
    ->multiCell(0, 6, 'This document is protected with AES-256 encryption.');

$encryptor = new Aes256Encryptor(
    ownerPassword: 'Str0ng!OwnerP@ss',
    userPassword:  'reader2026',
    permissions:   new Permissions(
        print:            true,
        printHighQuality: false,
        copy:             false,
        modify:           false,
        annotate:         true,
        fillForms:        true,
        extractForAccess: true,
        assemble:         false,
    ),
);

$pdf->encrypt($encryptor)
    ->save(__DIR__ . '/encrypted.pdf');

LGPL-3.0-or-later ライセンスの下で公開されています。