AEADのnonceを再利用すると何が壊れるのか: AES-GCMとChaCha20-Poly1305の場合
同じ鍵でnonceを2回使うとAES-GCMとChaCha20-Poly1305でそれぞれ何を失うのか、重複しないnonceをどう作るのかを、RFC 5116とRFC 8439に沿って整理します。
RFC 5116に登録されたAES-GCM(AEAD_AES_128_GCM、AEAD_AES_256_GCM)やRFC 8439のChaCha20-Poly1305で暗号化するときは、鍵とは別に12バイトのnonceを渡します。nonceは暗号文の隣にそのまま保存しても、一緒に送ってもかまいません。守るべき規則は1つで、同じ鍵で暗号化するときに同じnonceを2回使わないことです。この規則を破ったときに何を失うかはアルゴリズムによって違い、AES-GCMでは特に代償が大きくなります。
この記事は、AEADのインターフェースとAES-GCM・AES-CCMアルゴリズムを定義したRFC 5116(2008年)と、ChaCha20-Poly1305を定義しRFC 7539を置き換えたRFC 8439(2018年)を根拠にしています。どちらも2026-10-12に確認しました。GCMそのものの仕様であるNIST SP 800-38Dはこの記事の根拠には使っていません。そのため、GCMでnonce(IV)をランダムに生成する場合の上限などは扱いません。
AEADのインターフェースが定めていること
RFC 5116 §2.1では、認証付き暗号化(authenticated encryption)の入力は4つです。秘密鍵K、nonce N、平文P、そして暗号化せずに認証だけを行う関連データ(associated data)Aです。nonceへの要求は次のとおりです。
- 鍵の値が同じなら、異なる暗号化呼び出しに渡すnonceはすべて異なっていなければなりません(MUST)。唯一の例外は、その鍵で使うnonceがすべて長さ0の場合です。
- 長さは12オクテット(バイト)が推奨されます(SHOULD)。
- nonceは暗号文と一緒に保存・送信してもよく、復号の直前にメッセージの順序などの文脈から組み立て直してもかまいません。アルゴリズムの内部で認証されるので、Aに別途入れる必要もありません。
長さ0のnonceという例外は、AES-GCMとChaCha20-Poly1305には当てはまりません。この例外はランダム化(randomized)またはステートフル(stateful)なアルゴリズムのためのもので(§3、§4)、RFC 5116のAEAD_AES_128_GCMとAEAD_AES_256_GCM、RFC 8439のAEAD_CHACHA20_POLY1305は、いずれもnonce長の最小値と最大値(N_MIN、N_MAX)が12オクテットです。
大事なのは、RFC 5116の共通インターフェースが「nonceが重複したらこうなる」を1つに決めていない点です。§4は、nonceを誤って再利用したときにセキュリティがどう低下するかを、アルゴリズムの仕様ごとに説明するよう求めています(SHOULD)。そのため、結果はアルゴリズムごとに読む必要があります。
AES-GCM: 2つのメッセージの機密性と、その鍵による認証のすべて
RFC 5116 §5.1.1によれば、GCMで同じ鍵と同じnonceを使って異なる平文を2回暗号化すると、2つのものが崩れます。
- 機密性。 2つの暗号文を見た攻撃者は、2つの平文のXORを復元できます。
- 完全性。 攻撃者は完全性の保護に使われるGCM内部のハッシュ鍵を復元でき、その鍵を知れば以後の偽造は容易(trivial)になります。
GCMを特に危険にしているのは2つ目です。機密性の損失はnonceが重複した2つのメッセージに限られますが、認証の側ではRFCの表現どおり「その鍵が提供する認証と完全性の保護のすべて」が損なわれます。その鍵で作ったタグが合っていても、もはやメッセージが本物である根拠にはなりません。したがって、GCMでnonceの再利用が見つかったら、そのnonceを今後避けるだけでは足りず、鍵を交換する必要があります。この結論は§5.1.1の記述から導いたもので、RFCが鍵交換の手順を別に定めているわけではありません。
同じ節は、nonceの一意性を確実に保証できる場合にだけGCMを使うべきだとし、遅延を最小にするためのGCMの設計がこの脆弱性の原因だと説明しています。2回の暗号化で平文と関連データがどちらも同一だった場合は、上の深刻な結果は当てはまらないとも書かれています。2つの呼び出しの入力が完全に同じだった場合に限った例外なので、再利用してよいという意味に読んではいけません。
ChaCha20-Poly1305: キーストリームとワンタイム鍵が同じになる
RFC 8439 §4は、この文書を実装するうえで最も重要なセキュリティ上の考慮事項は、ChaCha20で使うnonceの一意性だと述べています。nonceが繰り返されると、2つのメッセージの間でキーストリームとPoly1305のワンタイム鍵がまったく同じになります。キーストリームが同じなので、2つの暗号文のXORがそのまま2つの平文のXORになり、平文のXORが露出します。
AEADの構成(§2.8)では、メッセージごとに鍵とnonceからPoly1305のワンタイム鍵を新しく作ります(§2.6)。Poly1305は名前のとおりワンタイムの認証子で、§2.5はその鍵の組(r, s)が呼び出しごとに一意であるべきで(should)、予測できないものでなければならない(MUST)と求めています。nonceを再利用すると、この鍵の組が2つのメッセージで同じになるので、その前提が崩れます。
ただし、RFC 8439はGCMのように「長期的に使う認証鍵を復元できる」とは書いていません。この場合に偽造がどこまで可能かも、別途述べていません。ですから「ChaCha20-Poly1305ならnonceを再利用しても認証は安全」と読んではいけませんし、逆にGCMの鍵復元の説明をそのまま当てはめてもいけません。RFCが明記している結果は、キーストリームとワンタイム鍵が同じになり、平文のXORが露出するというところまでです。
なお、RFC 5116 §5.3.1は、AES-CCMでnonceを再利用した結果として、2つの平文のXORが露出する機密性の損失を挙げています。
| アルゴリズム | 根拠 | 文書に書かれたnonce再利用の結果 |
|---|---|---|
| AES-GCM | RFC 5116 §5.1.1 | 2つの平文のXORが露出、内部ハッシュ鍵の復元によりその鍵の認証すべてが損なわれる |
| ChaCha20-Poly1305 | RFC 8439 §4 | キーストリームとPoly1305のワンタイム鍵が同一になる、2つの平文のXORが露出 |
| AES-CCM | RFC 5116 §5.3.1 | 2つの平文のXORが露出 |
重複しないnonceを作る
RFC 5116 §3.2が推奨する形式は、前方の固定フィールド(Fixed)と後方のカウンタ(Counter)をつなげたものです。実装は、12オクテットのnonceでカウンタが4オクテットの形式をサポートすべきです(SHOULD)。RFC 8439 §2.3は、複数の送信者が1つの鍵を共有する場合の例として、先頭32ビットを送信者ごとに一意にし、残り64ビットをカウンタで埋める分け方を挙げています。2つのフィールドの長さの配分は違いますが、形は同じです。
RFC 5116 §3.2 (12-octet nonce, 4-octet counter)
+--------------------------------+----------------+
| Fixed (8 octets) | Counter (4) |
+--------------------------------+----------------+
RFC 8439 §2.3 (multiple senders sharing one key)
+----------------+--------------------------------+
| Sender (4) | Counter (8 octets) |
+----------------+--------------------------------+
RFC 5116はこの形式を次のように説明しています。
- 推奨形式では、連続するnonceのカウンタフィールドは、ネットワークバイトオーダーの符号なし整数として見たときに単調増加する列になります。カウンタは0から始めて1ずつ増やすことが推奨され(SHOULD)、都合に応じて値を飛ばすことは許されます(MAY)。
- カウンタがCオクテットなら、作れるnonceは最大2^(8C)個です。4オクテットなら2^32個です。カウンタが上限に達したときに0へ戻すと、使用済みのnonceがまた出てくるので、その前に鍵を変える必要があります。
- 固定フィールドは1つの暗号化デバイスの中では変えてはならず、1つの鍵を複数のデバイスが使うならデバイスごとに異なっていなければなりません(MUST)。固定フィールドがFオクテットなら、1つの鍵を共有できるデバイスは最大2^(8F)台です。
RFC 8439もカウンタを勧めています。§2.6は、Poly1305の鍵生成に使うnonceは同じ鍵で呼び出しごとに一意でなければならないので、ランダムに生成してはならない(MUST NOT)とし、カウンタをよい実装方法として、LFSR(線形帰還シフトレジスタ)も許容される方法として挙げています。§4は、64ビットブロック暗号でカウンタを暗号化する方法も許容しますが、128ビットや256ビットのブロック暗号で暗号化したカウンタを切り詰めて使うのは、短時間で値が繰り返されうるため許容されないと付け加えています。
カウンタが巻き戻るとき
カウンタ方式が弱いのは、状態を失った瞬間です。RFC 5116はこうした状況を直接挙げています。
- 複数のデバイスやインスタンスが1つの鍵を使うとき。 nonceが重複しないよう互いに調整する必要があります(§3.1)。デバイスごとに異なる固定フィールドを置くのが簡単な方法です。
- 再起動をまたいで鍵を長く使うとき。 メモリにしかないカウンタは、再起動後に最初からやり直すことがあります。§3.1は、nonceを不揮発性ストレージにチェックポイントとして保存するよう求め、次の値が保存されたという確認を受けてから暗号化すれば、再利用を高い確度で防げると説明しています。毎回待つと遅延が大きくなるので、数段先の値を保存しておく方法も示しています。100を保存しておいて1から99までを使い、その間に200を保存する、というやり方です。
- 仮想マシンを以前の状態に戻すとき。 §8は、VMが以前の状態にロールバックされると、ソフトウェア実装がnonceを意図せず再利用しうると警告しています。
nonceの調整が難しい状況では、鍵を変えることで多くの再利用の問題を避けられる、というのも§3.1の助言です。
復号については別に見ておく必要があります。RFC 5116 §5.1.1は、GCMの復号処理に同じnonceの値を何度渡してもかまわないとしていますが、これはGCMの節の記述で、共通インターフェースの規則として書かれたものではありません。共通インターフェース側の§2.1は、nonceを誤って組み立て直して認証付き復号に渡しても、復号がそれを検出するのでセキュリティ上の失敗にはならないと説明しています。RFC 5116がすべてのAEADに求めるnonceの一意性は、あくまで暗号化の呼び出しについての規則です。ただし、ここまではすべて暗号処理のレベルの話です。§1は、AEADのインターフェースがリプレイ防止(anti-replay)のようなプロトコル上の問題を扱わないと明言しています。有効な暗号文を攻撃者が再送するのを防ぐのはプロトコルの役割なので、復号が繰り返されても害はないという意味に読んではいけません。
確認すること
- 1つの鍵に対してnonceを生成する箇所がいくつあるかを数えます。2つ以上なら、固定フィールドや送信者フィールドで空間を分けているかを確認します。
- カウンタがどこに保存されているかを見ます。メモリにしかないなら、再起動、スナップショットの復元、VMのロールバックの後に0からやり直さないかを確認します。
- カウンタの上限(4オクテットなら2^32)に達する前に鍵を交換しているかを確認します。
- AES-GCMで実際に再利用が起きたなら、その鍵はもう認証の用途には信頼できないものとみなし、交換します。
参考資料
いずれも2026-10-12に確認しました。
- RFC 5116: An Interface and Algorithms for Authenticated Encryption — §1、§2.1、§3、§3.1、§3.2、§4、§5.1、§5.1.1、§5.3.1、§8
- RFC 8439: ChaCha20 and Poly1305 for IETF Protocols — §2.3、§2.5、§2.6、§2.8、§4