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时牢记"唯一性"原则,避免硬编码,使用加密安全的随机数生成器。安全无小事,细节决定成败。


原始标题:Initialization Vector for Encryption | Baeldung