【実務・中級編】 AES-GCMにおけるNonce(IV)再利用による認証タグの脆弱性 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

AES-GCMの悪夢:その「Nonce再利用」が、あなたの暗号を無力化する

現場でコードレビューをしていると、暗号化の実装において「とりあえずAES-GCMを使えば安全だよね」という甘い認識に遭遇することがある。確かにAES-GCM(Galois/Counter Mode)は、暗号化と同時に改ざん検知(認証タグ)も行ってくれる現代の鉄板だ。

だが、この「鉄板」には致命的な弱点がある。それはNonce(IV: 初期化ベクトル)の再利用だ。これをやらかすと、認証タグが漏洩し、最悪の場合、暗号文の解読だけでなく、認証タグそのものを攻撃者が偽造できるようになる。

今日は、なぜNonceを使い回してはいけないのか、その「泥沼」のメカニズムと、明日から使えるセキュアな実装を叩き込む。

—

1. なぜNonceの再利用が「ゲームオーバー」なのか

AES-GCMは内部でカウンターモード(CTR)を使って暗号化し、GHASHという仕組みで認証タグを生成する。この認証タグを計算するための秘密鍵(ハッシュキー)は、実はNonceが重複した際に非常に脆弱になる。

攻撃者が同一のNonceで暗号化された2つの異なるメッセージを入手できた場合、特定の数学的演算(XOR)を行うだけで、この認証タグ生成用の内部鍵を導出できてしまう。

一度鍵が漏れれば、攻撃者は:
1. 既存の暗号文を改ざんする。
2. 任意の平文に対して、正しい認証タグを偽造する。

これでは、暗号化している意味が全くない。認証が突破されれば、Webアプリなら「認証トークンの偽造」や「設定情報の書き換え」に直結する。単なるデータ漏洩を超えた、システム完全性の崩壊だ。

—

2. セキュアなNonce生成の鉄則

AES-GCMでNonceを扱う際のルールはたった一つ。「決して同じNonceを、同じ鍵で二度使うな」だ。

  • ルール1: Nonceは必ず暗号論的に安全な乱数生成器(CSPRNG)で生成せよ。
  • ルール2: Nonce長は特別な理由がない限り96ビット(12バイト)で運用せよ(GCMの仕様上、効率と安全性のバランスが最適)。
  • ルール3: 鍵(Key)をローテーションするたびに、Nonceはリセットして構わない。

—

3. 実装サンプル:Pythonで学ぶ「安全な暗号化」

多くのエンジニアがやりがちなミスは、DBのIDや固定値をNonceに使うことだ。ここでは、os.urandom() を使った正しい実装例を示す。

import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

# 鍵の生成(本来はKey Management Service(KMS)等で厳重管理すること)
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)

def encrypt_data(plaintext: str):
    # 96ビット(12バイト)のランダムなNonceを生成
    nonce = os.urandom(12)
    
    # 暗号化と同時に認証タグが付与される
    ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
    
    # 送信/保存時には nonce + ciphertext を連結して保管する
    return nonce + ciphertext

def decrypt_data(data: bytes):
    # 先頭12バイトがNonce
    nonce = data[:12]
    ciphertext = data[12:]
    
    # 復号時にタグの検証も自動で行われる(失敗すればInvalidTag例外が出る)
    return aesgcm.decrypt(nonce, ciphertext, None).decode()

# 実行例
encrypted = encrypt_data("極秘データ")
print(f"暗号化データ: {encrypted.hex()}")

—

4. Webアプリ開発者が知るべき「落とし穴」

PHPやNode.jsで実装する場合も考え方は同じだが、以下の点に注意が必要だ。

JavaScript (Node.js) での実装ポイント

crypto.randomBytes(12) を必ず使用すること。ブラウザ環境(フロントエンド)で暗号化を行う場合は、window.crypto.getRandomValues() を使用しなければならない。Math.random() などという、セキュリティの「セ」の字もない関数は論外だ。

インフラ・運用の盲点

もし、ロードバランサーやWAFの設定で「リクエストの暗号化」を行っている場合、Nonceの生成・管理がアプリケーション層のロジックと疎結合になっていないか確認してほしい。特に分散システムでは、各サーバーが独立して乱数Nonceを生成しているかが鍵になる。UUIDなどをNonceの代わりにするような設計は、衝突リスクを考慮すると避けるべきだ。

—

5. まとめ:現場のエンジニアへ

セキュリティの本質は、「完璧な防御」を追い求めることではなく、「どこが壊れたら致命的か」を理解し、そのリスクを最短距離で排除することにある。

AES-GCMを使う際は、コードのどこかに「Nonceの再利用を防ぐためのチェック機構」があるか、あるいは「乱数生成器の品質は十分か」を自問自答してほしい。

もし、今運用しているコードで「NonceをDBの連番で管理している」といった実装があれば、即座に修正を計画すること。それが、インシデントハンドリングに追われる苦い夜を避けるための、唯一の道だ。

技術は常に攻撃者に先を越されやすい。だが、このレベルの「基本原則」を守るだけで、あなたのシステムは圧倒的に堅牢になる。明日からの実装に、ぜひ役立ててくれ。

コメント

タイトルとURLをコピーしました