対称暗号化

対称暗号化では、暗号化と復号に同じ鍵を使用します。このページで最も重要な考え方は、単純な暗号と AEAD(関連データ付き認証暗号)暗号の違いです。AES-CBC や AES-CTR 単体では機密性しか得られません。平文を読めない攻撃者でも暗号文のビットを反転させることができ、受信者は何も異常がないまま改ざんされた結果を復号してしまいます。AES-GCM と ChaCha20-Poly1305 は暗号化と同時に認証も行うため、改ざんは検出され、復号は明確に失敗します。特定の理由で単純なモードを使わざるを得ない場合を除き、AEAD 暗号を優先してください。

 

すべての関数はsgcCrypto_AES、sgcCrypto_Modes、sgcCrypto_ChaChaおよびsgcCrypto_Poly1305に含まれています。同じ鍵で IV やノンスを再利用しないでください。CTR と AEAD 暗号では、再利用は機密性を弱めるだけでは済まず、特に GCM では認証保証そのものが完全に破られます。メッセージごとにsgcRandomBytesで新しい値を生成してください。

 

AES-GCM(推奨)

 

ほとんどのプロトコルがデフォルトとして採用している AEAD モードです。ノンスは 12 バイトで、メッセージごとにランダムに生成してください。aAADは認証されますが暗号化はされません。これにより、暗号文をそのコンテキスト(たとえばメッセージ ID やプロトコルバージョン)に結び付けることができ、しかも暗号文を見られる相手からそのコンテキストを隠す必要がない場合に利用します。

 


function sgcAES_GCM_Encrypt(const aKey, aIV, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcAES_GCM_Decrypt(const aKey, aIV, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oIV, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oIV := sgcRandomBytes(12);
  oCipher := sgcAES_GCM_Encrypt(oKey, oIV, oPlain, oAAD, oTag);
  { send oIV, oCipher and oTag to the recipient }
  if not sgcAES_GCM_Decrypt(oKey, oIV, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

 

ChaCha20-Poly1305、XChaCha20-Poly1305

 

RFC 8439 が定める AEAD のペアです。AES-GCM が AES のハードウェアアクセラレーションに依存するのに対し、ChaCha20-Poly1305 は純粋なソフトウェアで一定の速度で動作します。これが、TLS や WireGuard が AES-NI を持たないモバイルや組み込みデバイスでこれをデフォルトとする理由です。ノンスは 12 バイトで、GCM と同じ制約があります。XChaCha20-Poly1305 はノンスを 24 バイトに拡張しており、カウンターを追跡することなく鍵の有効期間全体でランダムに選べるほど大きく、多数の独立した書き込み元が1つの鍵を共有しカウンターを互いに調整できない場合に重要になります。

 


function sgcChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;
function sgcXChaCha20Poly1305_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; out aTag: TBytes): TBytes;
function sgcXChaCha20Poly1305_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

 

AES-CCM

 

CBC-MAC 併用カウンターモードで、RFC 3610 および NIST SP 800-38C です。これは、制約のある環境や IoT スタックが好む AEAD です。ブロック暗号そのものだけで済み、GHASH テーブルを必要としないためで、Zigbee、802.15.4、Bluetooth、そして TLS の CCM スイートはすべてこれを指定しています。余裕がある場合は、GCM のほうが優れた選択のままです。CCM はデータに対して2パス行い、パイプライン化できないためです。

 

ノンスは 7 から 13 バイトで、その長さは自由に選べるものではありません。15 からノンス長を引いた値が長さフィールドのサイズになるため、それが最大メッセージサイズを決定し、13 バイトのノンスでは平文が 65535 バイトに制限されます。同じ鍵の下でノンスを再利用してはいけません。これは GCM とまったく同じです。aTagLenは 4、6、8、10、12、14 または 16 バイトです。

 


function sgcAES_CCM_Encrypt(const aKey, aNonce, aPlain, aAAD: TBytes; aTagLen: Integer; out aTag: TBytes): TBytes;
function sgcAES_CCM_Decrypt(const aKey, aNonce, aCipher, aAAD, aTag: TBytes; out aPlain: TBytes): Boolean;

var
  oKey, oNonce, oCipher, oTag, oPlain, oAAD: TBytes;
begin
  oNonce := sgcRandomBytes(12);
  oCipher := sgcAES_CCM_Encrypt(oKey, oNonce, oPlain, oAAD, 16, oTag);
  { send oNonce, oCipher and oTag to the recipient }
  if not sgcAES_CCM_Decrypt(oKey, oNonce, oCipher, oAAD, oTag, oPlain) then
    { the ciphertext, the tag or the AAD was altered: reject it };
end;

 

AES-GMAC

 

GMAC は、平文を空にして、メッセージ全体を追加データとして渡した状態で実行する AES-GCM そのものであり、これは標準がそう定義しているとおりです。したがって、GCM のノンスに関するルールをすべて引き継ぎます。aIVは通常 12 バイトで、同じ鍵の下で再利用してはいけません。GMAC の下で IV を再利用すると、単に弱くなるだけでなく、ハッシュサブ鍵が漏れて攻撃者がタグを完全に偽造できるようになるため、失敗は段階的ではなく完全なものになります。ハッシュ化 & メッセージ認証ページの AES-CMAC は IV をまったく必要とせず、プロトコルに既に GCM が組み込まれている場合を除けば、より安全な選択です。タグは 16 バイトです。

 


function sgcAES_GMAC(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_GMAC_Verify(const aKey, aIV, aData, aTag: TBytes): Boolean;

 

AES ブロックモード

 

これらは機密性のみを提供し、認証は行いません。平文に完全性が必要な場合はハッシュ化 & メッセージ認証の MAC と組み合わせるか、代わりに上記の AES-GCM を使用してください。

 


function sgcAES_CBC_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_CTR(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_CFB_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CFB_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;
function sgcAES_OFB(const aKey, aIV, aData: TBytes): TBytes;
function sgcAES_ECB_Encrypt(const aKey, aPlain: TBytes): TBytes;
function sgcAES_ECB_Decrypt(const aKey, aCipher: TBytes): TBytes;

 

sgcAES_CTRとsgcAES_OFBはそれ自体が逆演算になっています。復号するには同じ鍵と IV で同じ関数をもう一度呼び出してください。ECB は、それを指定する古い形式との相互運用のためだけに含まれています。同一の平文ブロックは同一の暗号文ブロックを生成してしまうため、データの構造が漏れてしまい、新規の用途で選ぶべきではありません。

 

CBC は、パディングをまったく行わないモードでも利用できます。Java がAES/CBC/NoPaddingと表記するモードです。入力長はちょうど 16 の倍数でなければならず、そうでない場合は、呼び出しが黙ってパディングを行い、相手側が読めない暗号文を生成してしまうのではなく、例外が発生します。プロトコルが既にブロック境界に揃ったデータを保証している場合にのみ安全に使用できます。

 


function sgcAES_CBC_Encrypt_NoPad(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CBC_Decrypt_NoPad(const aKey, aIV, aCipher: TBytes): TBytes;

 

Ciphertext stealing は、ブロックサイズの倍数でないデータに対して CBC のパディングによるオーバーヘッドを回避します。

 


function sgcAES_CTS_Encrypt(const aKey, aIV, aPlain: TBytes): TBytes;
function sgcAES_CTS_Decrypt(const aKey, aIV, aCipher: TBytes): TBytes;

 

パディング方式

 

ブロックモードは平文が 16 バイトの倍数であることを必要とし、パディング方式はそこに到達するための手段です。新規の用途で正しい選択は PKCS#7 だけであり、残りの6種類は、既に別の方式を選んでいるシステムとの間でデータを読み書きするために存在します。padPKCS5がこの一覧にないのは、16 バイトブロックにおいてはそれが PKCS#7 と同じものになるためです。両者の名前は、それぞれが定義されたブロックサイズが異なるだけです。

 


type
  TsgcPadding = (padNone, padPKCS7, padZero, padANSIX923, padISO7816_4,
    padISO10126_2, padTBC);

function sgcPad_Add(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding): TBytes;
function sgcPad_Remove(const aData: TBytes; aBlockSize: Integer; aPadding: TsgcPadding; out aPlain: TBytes): Boolean;

function sgcAES_CBC_EncryptPad(const aKey, aIV, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_CBC_DecryptPad(const aKey, aIV, aCipher: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_EncryptPad(const aKey, aPlain: TBytes; aPadding: TsgcPadding): TBytes;
function sgcAES_ECB_DecryptPad(const aKey, aCipher: TBytes; aPadding: TsgcPadding): TBytes;

var
  oKey, oIV, oPlain, oCipher: TBytes;
begin
  oCipher := sgcAES_CBC_EncryptPad(oKey, oIV, oPlain, padANSIX923);
end;

 

上記のsgcAES_CBC_EncryptとsgcAES_ECB_Encryptは、これらに対する PKCS#7 の簡略版です。sgcPad_Removeは、不正な形式のパディングに対して例外を発生させるのではなくFalseを返し、あらゆる種類の失敗に対して同じ経路をたどります。それらを区別できてしまうことこそが、パディングオラクルが必要とする情報だからです。特にpadZeroには注意してください。これはパディングバイトと末尾の平文ゼロを区別できないため、末尾のゼロをすべて取り除いてしまい、正当に末尾がゼロで終わるデータは切り詰められて返ってきます。

 

AES ハードウェアアクセラレーション(AES-NI)

Delphi で作成した Windows の Win32 および Win64 ビルドでは、sgcCrypto_AES が実行時に CPU を確認し、AES-NI、PCLMULQDQ、SSSE3 に対応していることが分かると、単一ブロック暗号化、AES-CTR、AES-GCM(カウンターモードと GHASH)、AES-CBC 暗号化をこれらの命令上で実行します。これらは設計上、定数時間です。対応していない場合、および Linux64、macOS、iOS、Android では、これまでどおり定数時間のソフトウェアコアが使用されます。CBC の復号は常にソフトウェアコアを使用します。ネイティブ TLS エンジンは、これを自動的に活用します。

sgcAES_HardwareAvailable は、このビルドがハードウェア経路を備えており、かつ CPU がそれに対応している場合に True を返します。グローバル変数 sgcAES_UseHardware(デフォルトは True)を False に設定すると、比較などの目的でソフトウェアコアを強制的に使用できます。これは起動時に一度だけ設定してください。

var sgcAES_UseHardware: Boolean = True;
function sgcAES_HardwareAvailable: Boolean;

if sgcAES_HardwareAvailable then
  Memo1.Lines.Add('AES-NI in use');

AES Key Wrap

 

鍵のラッピングはメッセージではなく鍵を保護するものです。IV は不要で、決定論的であり、ラップされた鍵を認証します。これは、データ暗号化鍵をマスター鍵の下で保管するための標準的な方法であり、たとえば HSM や KMS のマスター鍵がファイルごとの鍵をラップする場合などに使われます。RFC 3394 では、ラップする鍵の長さが 8 バイトの倍数であることが要求されますが、RFC 5649 では任意の長さの鍵に対応するためのパディングが追加されています。

 


function sgcAES_KeyWrap(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrap(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;
function sgcAES_KeyWrapPad(const aKEK, aPlainKey: TBytes): TBytes;
function sgcAES_KeyUnwrapPad(const aKEK, aWrapped: TBytes; out aPlainKey: TBytes): Boolean;

 

ChaCha20、Salsa20(生のストリーム暗号)

 

上記の AEAD 構成の基盤となるストリーム暗号そのものを、生の暗号を指定するプロトコルのために直接提供しています。どちらも認証は行わないため、このページの上のほうにある Poly1305 の AEAD バージョンのほうがほぼ常に適しています。Salsa20 は ChaCha20 の前身であり、NaCl 時代の形式との互換性のために残されています。新規の用途では ChaCha20 を選んでください。

 


function sgcChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcXChaCha20(const aKey, aNonce, aData: TBytes; aCounter: Cardinal): TBytes;
function sgcSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;
function sgcXSalsa20(const aKey, aNonce, aData: TBytes; aCounter: Int64): TBytes;

 

レガシー:DES と NTLM

 

sgcCrypto_Legacyは、ECB モードの単一 DES と、DES を基盤とする NTLM v1 の鍵導出も提供します。DES の実効鍵長は 56 ビットで、現代のハードウェアでは数時間で総当たり可能です。これは、名前でそれを指定するプロトコル(多くの場合 NTLM 認証)と相互運用するためだけに存在します。新規の用途では選択せず、代わりに AES を使用してください。

 


function sgcDES_EncryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_DecryptECB(const aKey, aData: TBytes): TBytes;
function sgcDES_NTLM7to8(const aKey7: TBytes): TBytes;