説明
CBCには予測できないIVが必要です。GCMでは、同じ鍵でIVを再利用してはいけません。同じ鍵とIVの組み合わせ、ゼロで埋めたIV、固定文字列から作ったIVを使うと、特にCBCの先頭ブロックで暗号文のパターンが漏れるおそれがあります。GCMのNonce再利用は機密性と完全性を損ない、暗号文同士のXORから平文の関係が分かったり、認証タグを偽造されたりするおそれがあります。
想定される影響
- パターンの露出: CBCで同じ鍵とIVを使うと、同じ先頭平文ブロックから同じ暗号文ブロックが生成されます。
- 平文間の関係の漏えい: 同じ鍵とIVで生成したGCM暗号文から、平文同士の関係を推測されるおそれがあります。
- 偽造: GCMのNonce再利用により認証が破られ、有効なタグを偽造されるおそれがあります。
- メッセージの関連付け: 繰り返されるパターンから、リクエストと応答の関係を推測される可能性があります。再送攻撃の防止には、別途プロトコル側の制御が必要です。
対処方法
- 暗号化のたびに新しいIVを生成してください。予測できない乱数IVには
SecureRandom.nextBytes(iv)を使用します。GCMでは通常12バイト(96ビット)が推奨されます。 new byte[N]で確保した配列をゼロのまま使ったり、"constant".getBytes()などの定数由来の値をIVにしたりしないでください。- IVを暗号文とともに保存・送信し、同じ鍵で再利用しないように管理してください。乱数IVにも衝突の可能性があるため、鍵ごとの使用量と鍵の交換も管理してください。
- 必要に応じて、AES-GCM-SIVなどNonceの誤用に耐性があるモードを検討してください。ただし、Nonceを重複させないことが基本です。
- 必要なIV長を守ってください。AES-CBCでは16バイト、GCMでは通常12バイトです。
例
変更前
java
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class StaticIvBad {
// BAD: 고정(제로) IV 사용으로 GCM 보안 붕괴
public byte[] encrypt(byte[] key, byte[] plaintext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec sk = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; // 모든 바이트가 0 -> 예측 가능
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, sk, spec);
return cipher.doFinal(plaintext);
}
}
変更後
java
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
public class RandomIvGood {
// GOOD: 매번 SecureRandom으로 새 IV 생성하고, IV를 암호문과 함께 저장/전달
public byte[] encrypt(byte[] key, byte[] plaintext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec sk = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; // GCM 권장 12바이트
SecureRandom.getInstanceStrong().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, sk, spec);
byte[] ciphertext = cipher.doFinal(plaintext);
// IV || C 형식으로 결합해 저장/전달
ByteBuffer out = ByteBuffer.allocate(iv.length + ciphertext.length);
out.put(iv).put(ciphertext);
return out.array();
}
}
解説:
- 変更前: 同じGCM鍵でゼロの固定IVを再利用すると、Nonceが重複し、平文の関係の露出やタグの偽造につながります。CBCでも固定IVはパターンを露出させます。
- 変更後: 暗号化のたびに
SecureRandomで12バイトのIVを生成し、暗号文とともに保存・送信します。乱数生成だけで絶対的な一意性は保証されないため、鍵ごとの使用量を制限し、IVの再利用を防いでください。