2. 加密算法
任何加密算法都需要接收明文和密钥来生成密文,同时也能用密文和相同密钥还原出原始明文。以分组密码为例,它通过加密/解密固定长度的数据块来提供安全性。我们使用不同的加密模式将算法重复应用于整个数据,并决定使用何种类型的初始化向量(IV)。
在分组密码中,我们处理相同大小的数据块。当明文小于块大小时,需要使用填充。某些模式(如流密码模式)则不需要填充。
3. 初始化向量(IV)
IV作为加密算法的起始状态,用于隐藏密文中的数据模式。这避免了每次调用后都需要重新生成新密钥的问题。
3.1. IV的特性
大多数加密模式都需要使用唯一序列作为IV,且绝不能用相同密钥重复使用相同IV。这确保即使使用相同密钥多次加密相同明文,也能产生不同的密文。
根据加密模式,IV具有以下关键特性:
- 必须不可重复
- 根据模式需要具有随机性
- 无需保密
- 需要是加密学nonce(仅使用一次的数值)
- AES的IV始终是128位,与密钥长度无关
3.2. 生成IV
可以直接从Cipher
类获取IV:
byte[] iv = cipher.getIV();
如果不确定默认实现,可以自定义生成方法。当不显式提供IV时,会隐式调用Cipher.getIV()
。只要符合上述特性,任何生成方法都可以。
方法1:使用SecureRandom生成随机IV
public static IvParameterSpec getIVSecureRandom(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] iv = new byte[Cipher.getInstance(algorithm).getBlockSize()];
random.nextBytes(iv);
return new IvParameterSpec(iv);
}
方法2:从Cipher参数提取IV
public static IvParameterSpec getIVInternal(Cipher cipher) throws InvalidParameterSpecException {
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
return new IvParameterSpec(iv);
}
以上方法都能生成随机、不可预测的IV。但注意:对于GCM等模式,IV需要与计数器结合使用。此时通常使用前12字节作为IV,后4字节作为计数器:
public static byte[] getRandomIVWithSize(int size) {
byte[] nonce = new byte[size];
new SecureRandom().nextBytes(nonce);
return nonce;
}
这种情况下需确保计数器不重复且IV唯一。
最后提个醒:虽然不推荐,但也可以使用硬编码IV(容易踩坑,慎用!)。
4. 不同模式下的IV使用
加密的核心目标是隐藏明文模式,防止攻击者猜测。我们使用不同加密模式来掩盖密文中的模式特征。
ECB、CBC、OFB、CFB、CTR、CTS和XTS等模式仅提供机密性,无法防止篡改。可通过消息认证码(MAC)或数字签名检测篡改。认证加密(AE)模式(如CCM、GCM、CWC等)则提供综合保护。
4.1. 电子密码本(ECB)模式
ECB模式用相同密钥独立加密每个块。致命缺陷:相同明文永远生成相同密文,无法隐藏数据模式。因此不推荐用于加密协议,解密时也易受重放攻击。
加密示例:
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
ciphertext = cipher.doFinal(data);
解密示例:
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
plaintext = cipher.doFinal(cipherText);
这里完全没使用IV,导致相同明文生成相同密文。虽然ECB最脆弱,但仍是许多提供者的默认模式(坑!)——务必显式指定加密模式。
4.2. 密码块链接(CBC)模式
CBC模式使用IV防止相同明文生成相同密文。关键点:IV必须真正随机或唯一,否则会退化到ECB模式的安全风险。
生成随机IV:
IvParameterSpec iv = CryptoUtils.getIVSecureRandom("AES");
加密时使用IV:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
解密时传递相同IV:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
4.3. 密码反馈(CFB)模式
CFB是最基础的流模式,类似自同步流密码。与CBC不同,它不需要填充。IV作为密码生成的流源——如果重复使用IV,密文中可能出现相似模式(和CBC一样,IV必须随机)。如果IV可预测,机密性将完全丧失。
生成CFB模式IV:
IvParameterSpec iv = CryptoUtils.getIVSecureRandom("AES/CFB/NoPadding");
极端情况:使用全零IV时,在CFB-8模式下,某些密钥可能生成全零IV和全零明文,导致1/256的密钥完全失效(直接返回明文作为密文)。
重要:CBC和CFB模式下重复使用IV会泄露不同消息间共享块的信息。
4.4. 计数器(CTR)和输出反馈(OFB)模式
CTR和OFB模式将分组密码转换为同步流密码,生成密钥流块。初始化时需指定IV,通常分配12字节给IV,4字节给计数器,这样可加密长度达2^32块的消息。
创建IV示例:
IvParameterSpec ivSpec = CryptoUtils.getIVSecureRandom("AES");
CTR模式注意事项: 初始比特流依赖IV和密钥。重复使用IV会导致密钥流重用,彻底破坏安全性。如果IV不唯一,对应重复计数器块的机密性会失效,但其他数据块不受影响。
4.5. Galois/计数器(GCM)模式
GCM是认证加密(AEAD)模式,结合了CTR模式加密和认证机制,保护明文和附加认证数据(AAD)。
但注意:GCM的认证依赖IV的唯一性。我们使用nonce作为IV,即使重复一个IV也可能使实现遭受攻击。
由于GCM使用AES加密,IV/计数器为16字节。通常前12字节作为IV,后4字节作为计数器。
创建GCM模式IV:
byte[] iv = CryptoUtils.getRandomIVWithSize(12);
加密初始化:
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
解密初始化:
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
同样需要唯一IV,否则可能破解明文。
4.6. IV需求总结
下表总结了不同模式对IV的要求:
模式 | IV要求 | 随机性 | 唯一性 | 保密性 |
---|---|---|---|---|
ECB | 不需要 | - | - | - |
CBC | 必需 | 是 | 是 | 否 |
CFB | 必需 | 是 | 是 | 否 |
CTR | 必需 | 否 | 是 | 否 |
OFB | 必需 | 是 | 是 | 否 |
GCM | 必需 | 否 | 是 | 否 |
核心原则: 相同密钥下重复使用IV会导致安全性丧失。优先使用GCM等高级模式。标准JCE不提供CCM等模式时,可使用Bouncy Castle实现。
5. 结论
本文探讨了在不同加密模式下使用IV的方法,分析了常见问题和最佳实践。关键要点:
- IV的核心价值是确保相同明文生成不同密文
- 不同模式对IV的要求差异显著(随机性/唯一性)
- 重复使用IV是常见安全漏洞(尤其CBC/CFB/GCM)
- 生产环境推荐使用GCM等认证加密模式
终极建议: 处理IV时牢记"唯一性"原则,避免硬编码,使用加密安全的随机数生成器。安全无小事,细节决定成败。