固定IV在AES加密中的致命隐患
使用固定的IV(初始化向量)在AES加密中是一个严重的安全漏洞。
下面我将详细解释为什么这是问题,以及如何正确修复它。
为什么固定IV是危险的?
以最常用的CBC模式为例:
破坏语义安全性:相同的明文会生成相同的密文,攻击者可以通过分析密文模式获取信息。
重放攻击:攻击者可以记录和重放有效的密文块。
部分明文恢复:在已知部分明文的情况下,可能推导出其他明文内容。
安全的IV使用原则
IV必须是:
随机且不可预测:每次加密都生成新的随机IV
唯一性:在相同密钥下,IV绝不能重复使用
不需要保密:IV可以随密文一起存储/传输
修复方案
方案1:随机IV(推荐)
运行
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;public class SecureAESUtil {private static final String ALGORITHM = "AES/CBC/PKCS5Padding";private static final int IV_LENGTH = 16; // 128 bits for AES// 加密:生成随机IVpublic static String encrypt(String plaintext, SecretKey key) throws Exception {byte[] iv = new byte[IV_LENGTH];SecureRandom random = new SecureRandom();random.nextBytes(iv); // 生成随机IVCipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));byte[] ciphertext = cipher.doFinal(plaintext.getBytes());// 将IV和密文组合在一起:IV + 密文byte[] encryptedData = new byte[IV_LENGTH + ciphertext.length];System.arraycopy(iv, 0, encryptedData, 0, IV_LENGTH);System.arraycopy(ciphertext, 0, encryptedData, IV_LENGTH, ciphertext.length);return Base64.getEncoder().encodeToString(encryptedData);}// 解密:从密文中提取IVpublic static String decrypt(String encryptedData, SecretKey key) throws Exception {byte[] data = Base64.getDecoder().decode(encryptedData);// 提取前16字节作为IVbyte[] iv = new byte[IV_LENGTH];byte[] ciphertext = new byte[data.length - IV_LENGTH];System.arraycopy(data, 0, iv, 0, IV_LENGTH);System.arraycopy(data, IV_LENGTH, ciphertext, 0, ciphertext.length);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));byte[] plaintext = cipher.doFinal(ciphertext);return new String(plaintext);}
}方案2:使用GCM模式(更推荐)
GCM模式提供了认证功能,更安全:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;public class SecureAESGCMUtil {private static final String ALGORITHM = "AES/GCM/NoPadding";private static final int IV_LENGTH = 12; // 96 bits recommended for GCMprivate static final int TAG_LENGTH = 128; // 128-bit authentication tagpublic static String encryptGCM(String plaintext, SecretKey key) throws Exception {byte[] iv = new byte[IV_LENGTH];new SecureRandom().nextBytes(iv);Cipher cipher = Cipher.getInstance(ALGORITHM);GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);byte[] ciphertext = cipher.doFinal(plaintext.getBytes());// 组合:IV + 密文byte[] encryptedData = new byte[IV_LENGTH + ciphertext.length];System.arraycopy(iv, 0, encryptedData, 0, IV_LENGTH);System.arraycopy(ciphertext, 0, encryptedData, IV_LENGTH, ciphertext.length);return Base64.getEncoder().encodeToString(encryptedData);}public static String decryptGCM(String encryptedData, SecretKey key) throws Exception {byte[] data = Base64.getDecoder().decode(encryptedData);byte[] iv = new byte[IV_LENGTH];byte[] ciphertext = new byte[data.length - IV_LENGTH];System.arraycopy(data, 0, iv, 0, IV_LENGTH);System.arraycopy(data, IV_LENGTH, ciphertext, 0, ciphertext.length);Cipher cipher = Cipher.getInstance(ALGORITHM);GCMParameterSpec parameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);byte[] plaintext = cipher.doFinal(ciphertext);return new String(plaintext);}
}如何迁移现有系统?
如果已有使用固定IV加密的数据,需要:
重新加密所有数据:用新方法(随机IV)重新加密所有现有数据
版本标识:在加密数据中加入版本标识,区分新旧加密方式
逐步迁移:在解密时根据版本选择相应算法
安全要点总结
✅ 每次加密都使用随机IV
✅ IV随密文一起存储/传输
✅ 考虑使用GCM模式替代CBC
✅ 使用安全的随机数生成器(SecureRandom)
❌ 绝对不要使用固定IV
❌ 不要在相同密钥下重复使用IV
修复这个漏洞对于保护数据安全至关重要,建议尽快实施修复方案。
