为什么我的AES Cipher在初始化DECRYPT_MODE时会抛出InvalidKeyException?

5

为什么这个初始化会成功:

Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, secRandom);

当出现以下情况时会失败:

Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.DECRYPT_MODE, secretKey, secRandom);

在主线程中抛出异常 "main" java.security.InvalidKeyException: 参数缺失。

secretKey由KeyGenerator生成,secureRandom是通过使用随机静态种子设置的SecureRandom.getInstance("SHA1PRNG")生成的。

谢谢


1
为什么要将 RNG 传递给解密操作?我猜测 RNG 用于生成 IV,因此在解密时您可能需要传递 IV 而不是 RNG。 - CodesInChaos
那么为什么init首先要获取一个secRandom呢?正确的方法是首先从secRandom中获取一些字节,将它们保存为IV,然后使用新的IvParameterSpec来使用它们吗?稍后以相同的方式将IV传递给解密? - user54000
2
它可能需要RNG,这样它就可以自己创建IV,而不必麻烦您。但我不熟悉Java加密API。 - CodesInChaos
1
@CodesInChaos:啊,但你在所有方面都是正确的。 - President James K. Polk
1
@CodesInChaos 您是正确的,但是“SHA1PRNG”的SecureRandom实现确实为静态种子创建了一个静态值(如果使用正确)。当然,init调用无法知道它静态值,因此它将简单地退出并显示JavaDoc中指定的异常。 - Maarten Bodewes
2个回答

5
正如CodeInChaos所猜测的那样,当使用Cipher.ENCRYPT_MODE创建AESCipher实例时,SecureRandom实例用于派生随机IV。但是,在解密模式下创建Cipher实例时,您需要将其作为参数提供。这个无意义的小代码片段展示了一个例子。
public static void main(String[] args) throws Exception {
    SecureRandom secRandom = SecureRandom.getInstance("SHA1PRNG");
    KeyGenerator kg = KeyGenerator.getInstance("AES");
    kg.init(128, secRandom);
    Key secretKey = kg.generateKey();
    Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, secRandom);
    IvParameterSpec iv = new IvParameterSpec(AESCipher.getIV());
    AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    AESCipher.init(Cipher.DECRYPT_MODE, secretKey,iv, secRandom);
}

此外,您声称使用静态种子初始化SecureRandom实例表明您对该类的误解。SecureRandom不能保证在提供相同种子时会得到相同的输出。如果您仔细查看Javadocs,您会发现它会尽可能地从其他来源提供一些真正的熵。

编辑1:

感谢owlstead在审查答案方面的彻底性。有关讨论的其他内容,请参见他回答的相关问题。 SHA1PRNG的源代码可在此处在线获取。虽然有点棘手,但如果您在请求任何随机字节之前提供种子,则输出将完全确定。因此,我先前的说法是不正确的。


额,我想是个小错误,“SHA1PRNG”实现Java保证在提供相同种子的情况下具有相同的输出,如果在调用检索随机数据的任何方法之前设置了种子,请参阅我的答案这里和JavaDoc的SecureRandom类。 - Maarten Bodewes
请注意,使用此方法生成密钥是非常不明智的。输出结果没有足够的规范性,不能用于从静态密钥材料中生成密钥或初始化向量(IV)。因此,在这个意义上,这个备注是正确的:不要使用SecureRandom来生成基于静态密钥材料的密钥或初始化向量(IV) - Maarten Bodewes

2
只需阅读您正在使用的带有SecureRandom的init方法的JavaDoc:

如果此密码需要任何无法从给定密钥派生的算法参数,则底层密码实现应在加密或密钥封装时自动生成所需的参数(使用特定于提供程序的默认值或随机值),如果它正在进行解密或密钥解包,则引发InvalidKeyException。生成的参数可以使用getParametersgetIV(如果参数是IV)检索。

您将不得不将加密后的IV传输到解密方法中,例如通过将其前置到密码文本中。 IV可以明文传输。对于解密,请使用IvParameterSpec设置IV,而不是使用SecureRandom


1
就我个人而言,我认为Java加密API中生成默认值或提供程序特定值的整个概念是愚蠢的功能,因此我总是使用IvParameterSpec,并在加密过程中自己填充随机值。这样更容易调试,并且加密和解密调用也非常对称。 - Maarten Bodewes
同意。我不会说默认值在理论上总是会导致灾难,但我从未见过一个实现,它们没有引起比解决更多的问题。不要在Java加密API中使用默认值,也不要使用默认字符集...只需避免使用默认值。 - President James K. Polk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接