Technical Article

巨大なドキュメントのための高速AES-256 PDF暗号化

法務、金融、ヘルスケアの分野では、大量のPDFドキュメントを生成することが標準的な慣行です。しかし、ドキュメントの作成は戦いの半分にすぎず、それらを保護することも同様に重要です。毎日数百ギガバイトのPDFを処理するアーカイブパイプラインがある場合、AES-256暗号化の適用はすぐにパフォーマンスのボトルネックになる可能性があります。

この記事では、メモリの枯渇を回避し、暗号化ループを最適化することで、Delphiで高速なAES-256 PDF暗号化を実現する方法を探ります。

PDF暗号化仕様

PDFのセキュリティは大きく進化しました。初期のバージョンでは40ビットのRC4が使用されていましたが、現在では簡単に破られてしまいます。現在の標準(PDF 1.7拡張レベル3およびPDF 2.0)では、AES-256暗号化が義務付けられています。

PDFでは、ファイルブロック全体を暗号化するわけではありません。ドキュメント構造(XRefテーブルや辞書の構造)は平文のままです。代わりに、ストリーム(画像やページコンテンツの生データ)と文字列(メタデータテキストなど)を暗号化します。これには、パーサーがデータを抽出し、AES CBC(暗号ブロック連鎖)を適用して、書き戻す必要があります。

ボトルネック: メモリへのロード

巨大なPDF(たとえば、2GBのスキャン済みアーカイブ)を暗号化する際によくある間違いは、暗号化エンジンに渡す前にストリーム全体を`TMemoryStream`にロードすることです。これにより、32ビットプロセスではメモリ不足(OOM)例外が発生し、64ビットプロセスでは大量のページフォールトが発生します。

Delphiでのストリーミング暗号化

解決策は、チャンク化されたストリーミングアプローチを使用することです。Windows Cryptography API: Next Generation (CNG) またはOpenSSLのようなライブラリを使用して、PDFストリームを64KBのブロックで読み取り、ブロックを暗号化し、出力ディスクストリームに直接書き込むことができます。

ストリームのバッファリングされた暗号化ループを示す概念的なDelphiの例を次に示します。

uses
  System.Classes, System.SysUtils;

const
  BUFFER_SIZE = 65536; // 64KB chunks

// This represents your AES-256 encryption routine
procedure EncryptStreamChunk(const InBuffer; var OutBuffer; BytesRead: Integer; const Key: TBytes; const IV: TBytes);
begin
  // Call to Windows CNG (BCryptEncrypt) or OpenSSL (EVP_EncryptUpdate)
  // ...
end;

procedure EncryptLargePDFStream(InputStream, OutputStream: TStream; const Key, IV: TBytes);
var
  InBuffer, OutBuffer: array of Byte;
  BytesRead: Integer;
begin
  SetLength(InBuffer, BUFFER_SIZE);
  // AES CBC requires padding, so the output buffer must be slightly larger
  SetLength(OutBuffer, BUFFER_SIZE + 16); 

  InputStream.Position := 0;
  OutputStream.Position := 0;

  repeat
    BytesRead := InputStream.Read(InBuffer[0], BUFFER_SIZE);
    if BytesRead > 0 then
    begin
      EncryptStreamChunk(InBuffer[0], OutBuffer[0], BytesRead, Key, IV);
      // Write the encrypted cipher text directly to disk
      OutputStream.Write(OutBuffer[0], BytesRead); // Note: padding logic omitted for brevity
    end;
  until BytesRead < BUFFER_SIZE;
end;

暗号化バックエンドの最適化

Delphi開発者には、AESバックエンドとしていくつかの選択肢があります。

  • ネイティブのDelphi実装: デプロイは簡単ですが、純粋にソフトウェアで実行されるため、遅くなることがよくあります。
  • Windows CNG (BCrypt): 高度に最適化されており、ハードウェアアクセラレーション(最新のIntel/AMD CPUのAES-NI命令)を利用できます。
  • OpenSSL (libcrypto): 業界標準であり、信じられないほど高速ですが、外部DLLを同梱する必要があります。

高スループットのサーバーアプリケーションでは、ハードウェアで高速化されたAES-NIが必須です。DelphiでWindows CNGを使用する場合、BCryptEncrypt関数をマッピングすることで、アプリケーションは重い処理をCPUの専用暗号化シリコンにオフロードでき、暗号化のオーバーヘッドを実質的にほぼゼロに減らすことができます。

結論

ギガバイト規模のPDFを暗号化する場合は、完全なメモリバッファリングではなくストリームのチャンク化に依存し、暗号化バックエンドがハードウェアAES-NIアクセラレーションを利用していることを確認してください。この組み合わせにより、アーカイブパイプラインがCPUバウンドにならずに、NVMeドライブの速度で確実に実行されるようになります。

注: チャンク化されたストリーミングを利用した高速AES-256暗号化は、HotPDF VCL Componentでネイティブにサポートされています。