DelphiでOpenPGP:GnuPGなしで暗号化、署名、検証

· コンポーネント
DelphiでOpenPGP:GnuPGなしで暗号化、署名、検証

OpenPGPは今も多くの組織でファイル交換に使われています。銀行は支払いバッチを自社の公開鍵で暗号化することを求め、パートナーはGnuPGで暗号化したレポートを送ってきて、リリースには分離署名を付けて出荷しなければなりません。Delphiからこれを行うには、通常はすべてのマシンにGnuPGをインストールし、コマンドラインからgpg.exeを呼び出す必要があり、パス、キーリング、終了コードの扱いがすべて付いて回りました。

sgcCryptoは、純粋なObject PascalでOpenPGPを自前で実装しました。配布するDLLも、呼び出すコマンドラインツールも、Indyへの依存もありません。鍵、暗号化、復号、署名、検証はTBytesに対する数回の関数呼び出しで行え、結果は双方向でGnuPGと相互運用できます。

バージョン4とバージョン6

形式の2つの世代に対応しています。

複数の受信者に向けて暗号化する場合、ライブラリは全員が読める形式を選びます。全員が対応していればRFC 9580のAEAD形式、そうでなければ従来の完全性保護形式です。古い鍵と新しい鍵が混在する宛先へのメッセージでも、どこでも開けます。

鍵の生成

1回の呼び出しで完全な鍵が作成されます。主署名鍵、暗号化サブキー、ユーザーID、そしてそれらを結び付ける自己署名です。

uses
  sgcCrypto_PGP, sgcCrypto_PGP_Cert;

var
  oAlice: TsgcPGPKey;
begin
  // v4 Ed25519 + Curve25519, the default key type of GnuPG
  oAlice := sgcPGP_GenerateKey('Alice <alice@example.com>', 'secret',
    pgpkEd25519, 4);
  try
    Memo1.Text := oAlice.SaveToArmored(False);  // public key, share it
    SaveSecretKey(oAlice.SaveToArmored(True));  // protected secret key
  finally
    oAlice.Free;
  end;
end;

鍵の種類は、RSA 2048、3072、4096、Ed25519、Ed448(バージョン6)、そしてNIST P-256、P-384、P-521上のECDSAとECDHです。Brainpool鍵はインポートできます。秘密鍵はパスフレーズで保護され、バージョン4では反復SHA-256、バージョン6ではAES-OCBを用いたArgon2が使われます。

暗号化と復号

1つ以上の公開鍵に向けて暗号化し、必要に応じて送信者が署名します。出力はデフォルトでASCIIアーマー形式なので、そのままメールに添付したりサーバーにアップロードしたりできます。

// encrypt to Bob, signed by Alice, compressed with ZIP
vEncrypted := sgcPGP_Encrypt(vData, [oBob], oAlice);

// or protect a file with a passphrase only
vEncrypted := sgcPGP_EncryptSymmetric(vData, 'shared passphrase');

復号には、自分の秘密鍵を入れたキーリングを渡します。ロックされた鍵はパスフレーズでプライベートなコピー上でロック解除されるため、キーリング自体は変更されません。結果から、メッセージの宛先、暗号化方法、内部の署名が有効かどうかが分かります。

var
  oRing: TsgcPGPKeyRing;
  vResult: TsgcPGPDecryptResult;
begin
  oRing := TsgcPGPKeyRing.Create;
  try
    oRing.LoadFromFile('bob-secret.asc');
    vData := sgcPGP_Decrypt(vEncrypted, oRing, 'bob passphrase', oContacts,
      vResult);
    if vResult.Verify.Valid then
      Log('Signed by ' + vResult.Verify.SignerUserID);
  finally
    oRing.Free;
  end;
end;

署名と検証

ファイルやリリース向けの分離署名、インライン署名付きメッセージ、読めるままにしておきたいテキスト向けのクリアテキスト署名にすべて対応しており、改行コードに依存しない署名のためのテキストモードもあります。

// detached signature, the classic file.sig
vSignature := sgcPGP_Sign(vData, oAlice);
if sgcPGP_VerifyDetached(vData, vSignature, oContacts, vVerify) then
  Log('Valid signature from ' + vVerify.SignerUserID);

// cleartext signed text, readable without any tool
vSigned := sgcPGP_ClearSign('Payment batch 2026-10 approved', oAlice);

GnuPGとの互換性

すべての機能をGnuPG 2.4.5と双方向でテストしました。GnuPGで作成したファイルと署名はDelphiで復号・検証でき、ライブラリが生成するものはすべてgpg --decryptとgpg --verifyに受け入れられます。これには、GnuPG 2.4が自分で生成した鍵に対してデフォルトで書き出すOCB暗号化メッセージも含まれ、多くのOpenPGPライブラリはこれを読めません。バージョン6への対応は、RFC 9580のサンプル鍵とメッセージで確認しました。

鍵はアーマーテキストとして両方の世界の間を行き来できます。Delphiから鍵をエクスポートしてgpg --importを実行するか、gpg --export --armorの出力をTsgcPGPKeyRingに読み込んでください。

信頼できない入力に備えた設計

外部から届いたファイルを復号するアプリケーションは、その一部が悪意あるものだと想定しなければなりません。ライブラリは、古い無保護の暗号化形式を拒否し、復号の失敗はすべて同じエラーで報告してオラクルとして悪用されないようにし、文書へのSHA-1署名を拒否し、さらに細工されたメッセージでサーバーを占有できないよう、パスフレーズの作業係数と展開後サイズに上限を設けています。

その他の新機能

OpenPGPのunitは、Delphi 7からDelphi 13、およびC++Builderで、Win32とWin64向けにコンパイルできます。DSA鍵とElGamal鍵は読み込めますが使用はできず、古いCAST5、3DES、IDEA暗号には対応していません。

関連記事

特定の統合、銀行のフォーマット、インポートが必要な鍵についてご質問がありますか?お問い合わせください。コードを書いた本人たちから返信が届きます。