如果我使用IvParameterSpec初始化AES密码,与不使用有什么区别?

19

我在想,如果我使用IvParameterSpec来初始化AES密码,与不使用有什么区别?

使用IvParameterSpec的情况

SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));

没有 IvParameterSpec

SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);

我尝试使用一些示例测试数据进行测试,它们的加密和解密结果相同。

但由于我不是安全专家,我不想错过任何东西,并造成潜在的安全漏洞。我想知道,哪种方法才是正确的?


JavaDoc中指出,如果需要,密码将初始化自己的参数。因此,您可以提供标准参数,如果缺少,则会生成相同的结果。您尝试过不同的参数吗? - Tom
3个回答

29
一些背景知识(如果你已经知道这些,我很抱歉,只是确保我们使用相同的术语):
- AES是一种块密码,一种在128位块上操作的加密算法。 - CBC是一种块密码模式,一种使用块密码加密大量数据的方式。 - 块密码模式需要一个初始化向量(IV),它是一块初始化数据,通常与底层密码的块大小相同。
(维基百科关于块密码模式的文章 - http://en.wikipedia.org/wiki/Block_cipher_mode - 很好,清楚地解释了为什么需要IV。)
不同的块密码模式对IV选择过程有不同的要求,但它们都有一个共同点: 永远不要使用相同的IV和密钥加密两个不同的消息。 如果这样做,攻击者通常可以获取你的明文,有时还可以获取你的密钥(或等效的有用数据)。
CBC对IV有一个额外的限制,即IV必须对攻击者来说是不可预测的 - 所以artjom-b建议使用SecureRandom来生成IV是一个很好的建议。
此外,正如artjom-b所指出的那样,CBC只能为您提供保密性。在实践中,这意味着您的数据是保密的,但不能保证它完整无缺地到达。理想情况下,您应该使用一种经过身份验证的模式,比如GCM、CCM或EAX。
使用其中一种模式是一个非常好的主意。即使对于专家来说,加密然后进行消息认证码(Encrypt-then-MAC)也很不方便;如果可以的话,应该避免使用它。(如果必须使用它,请记住您必须为加密和消息认证码使用不同的密钥。)

2
当您说“不得使用相同的IV和密钥加密两条不同的消息”时,我们该怎么办呢?从您的评论中,听起来我们不能检查IV并且应为每个单独的消息生成一个新的IV,但我们需要IV来解密消息。 - Richard Tingle
1
回答自己的问题:似乎iv可以是公共信息,因此在加密后只需将其附加到字符串前面即可。 - Richard Tingle
那么盐是用来干什么的?我的意思是,盐就是为了这个目的而存在的,不是吗? - mjs
是的,没错;你可以直接在消息前面添加初始化向量(IV)。CBC模式要求IV是不可预测的,并不意味着它需要保密,只是你的攻击者事先不知道它是什么。 - undefined
盐值解决了一个完全不同的问题:大型密码数据库的安全存储。它使得预先计算大量密码哈希表变得困难,因为盐值是在生成密码的同时生成的,并且每个密码的盐值都是不同的。 - undefined

11

默认情况下,当您加密时,您的密码会生成一个随机IV。当您解密该数据时,必须使用确切的IV。

好消息是,IV不是秘密,您可以将其存储在公共位置。主要思想是为每个加密-解密操作保持不同的IV。

大多数情况下,您需要加密-解密各种数据,并为每个数据存储每个IV很麻烦。这就是为什么IV通常会与加密数据一起存储在单个字符串中,作为固定大小的前缀,以便在解密字符串时,您确定前16个字节(在我的情况下)是IV,其余字节是加密数据,需要进行解密。

您的有效载荷(要存储或发送)将具有以下结构:

[{未加密的IV固定长度}{使用秘密密钥加密的数据}]

这里分享我的encrypt和decrypt方法,我使用AES、256位秘密密钥、16位IV、CBC模式和PKCS7Padding。正如Justin King-Lacroix上面所述,最好使用GCM、CCM或EAX块模式。不要使用ECB!encrypt() 方法的结果是安全的,并且可以随时存储在数据库中或发送到任何地方。

注意:您可以在注释中使用自定义IV-只需将new SecureRandom()替换为new IvParameterSpec(getIV())(您可以在此输入静态IV,但这强烈不推荐)。

private Key secretAes256Key是具有秘密密钥的类字段,在构造函数中初始化。

private static final String AES_TRANSFORMATION_MODE = "AES/CBC/PKCS7Padding"

encrypt()方法:

public String encrypt(String data) {
    String encryptedText = "";

    if (data == null || secretAes256Key == null) 
        return encryptedText;
    }

    try {
        Cipher encryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
        encryptCipher.init(Cipher.ENCRYPT_MODE, secretAes256Key, new SecureRandom());//new IvParameterSpec(getIV()) - if you want custom IV

        //encrypted data:
        byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes("UTF-8"));

        //take IV from this cipher
        byte[] iv = encryptCipher.getIV();

        //append Initiation Vector as a prefix to use it during decryption:
        byte[] combinedPayload = new byte[iv.length + encryptedBytes.length];

        //populate payload with prefix IV and encrypted data
        System.arraycopy(iv, 0, combinedPayload, 0, iv.length);
        System.arraycopy(encryptedBytes, 0, combinedPayload, iv.length, encryptedBytes.length);

        encryptedText = Base64.encodeToString(combinedPayload, Base64.DEFAULT);

    } catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException | InvalidKeyException e) {
        e.printStackTrace();
    }
        
    return encryptedText;
}

这里是decrypt()方法:

public String decrypt(String encryptedString) {
    String decryptedText = "";

    if (encryptedString == null || secretAes256Key == null) 
        return decryptedText;
    }

    try {
        //separate prefix with IV from the rest of encrypted data
        byte[] encryptedPayload = Base64.decode(encryptedString, Base64.DEFAULT);
        byte[] iv = new byte[16];
        byte[] encryptedBytes = new byte[encryptedPayload.length - iv.length];

        //populate iv with bytes:
        System.arraycopy(encryptedPayload, 0, iv, 0, 16);

        //populate encryptedBytes with bytes:
        System.arraycopy(encryptedPayload, iv.length, encryptedBytes, 0, encryptedBytes.length);

        Cipher decryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
        decryptCipher.init(Cipher.DECRYPT_MODE, secretAes256Key, new IvParameterSpec(iv));

        byte[] decryptedBytes = decryptCipher.doFinal(encryptedBytes);
        decryptedText = new String(decryptedBytes);

    } catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
        e.printStackTrace();
    }

    return decryptedText;
}
希望这可以帮助到你。

Hope this helps.

翻译后为:

希望这可以帮助到你。


1
谢谢你的示例,Kirill。它对我非常有帮助。 我注意到 AES_TRANSFORMATION_MODE 应该是 "AES/CBC/PKCS5Padding"(而不是 PKCS7)。 - Juan

7

当没有提供IvParameterSpec时,Cipher应该自己初始化一个随机IV,但在您的情况下,它似乎没有这样做(new byte [16]是一个填充了0x00字节的数组)。看起来Cipher实现存在问题。在这种情况下,您应该始终提供一个新的随机IV(这对于语义安全性是必要的)。

通常是这样做的:

SecureRandom r = new SecureRandom(); // should be the best PRNG
byte[] iv = new byte[16];
r.nextBytes(iv);

cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));

当你发送或存储密文时,应将IV放在其前面。解密时,只需要从密文前面切片IV即可使用。它不需要保密,但应该是唯一的。
请注意,仅使用CBC模式只能提供机密性。如果任何类型的密文操作(恶意或非恶意)是可能的,则应使用像GCM或EAX这样的已认证模式。这些还将为您提供完整性以及机密性。如果您无法访问它们(SpongyCastle具有它们),则可以在加密后使用消息认证码(MAC)中的加密-然后-MAC方案,但正确实现它要困难得多。

但是,如果我使用随机IV进行加密,然后使用另一个随机IV进行解密,我的解密不会失败吗? - Cheok Yan Cheng
1
是的,这就是为什么初始化向量应该被添加到密文前面,这样你在解密时可以直接使用它。我已经相应地更新了我的答案。 - Artjom B.

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