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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29575024/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 15:31:24  来源:igfitidea点击:

Is there any difference, if I init AES cipher, with and without IvParameterSpec

javaandroidsecurityencryptionaes

提问by Cheok Yan Cheng

I was wondering, is there any difference, if I init AES cipher, with and without IvParameterSpec?

我想知道,如果我使用和不使用 IvParameterSpec 初始化 AES 密码,有什么区别吗?

With IvParameterSpec

使用 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]));

Without IvParameterSpec

没有 IvParameterSpec

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

I tested with some sample test data, their encryption and decryption result yield the same.

我用一些样本测试数据进行了测试,它们的加密和解密结果相同。

However, since I'm not the security expert, I don't want to miss out anything, and create a potential security loop hole. I was wondering, which is the correct way?

但是,由于我不是安全专家,因此我不想错过任何内容,从而造成潜在的安全漏洞。我想知道,哪个是正确的方法?

回答by Justin King-Lacroix

A bit of background (I'm sorry if you already know this, it's just worth making sure we're using the same terminology):

一些背景知识(如果您已经知道这一点,我很抱歉,确保我们使用相同的术语是值得的):

  • AES is a block cipher, an encryption algorithm that operates on 128-bit blocks.
  • CBC is a block cipher mode, a way of using a block cipher to encrypt large amounts of data.
  • Block cipher modes need an initialisation vector(IV), which is a block of initialisation data, usually the same size as the block size of the underlying cipher.
  • AES 是一种分组密码,一种对 128 位块进行操作的加密算法。
  • CBC 是一种分组密码模式,一种使用分组密码对大量数据进行加密的方式。
  • 块密码模式需要一个初始化向量(IV),它是一个初始化数据块,通常与底层密码的块大小相同。

(The Wikipedia on block cipher modes - http://en.wikipedia.org/wiki/Block_cipher_mode- is really good, and makes it clear why you need an IV.)

(关于分组密码模式的维基百科 - http://en.wikipedia.org/wiki/Block_cipher_mode- 非常好,并且清楚地说明了为什么需要 IV。)

Different block modes impose different requirements on the IV selection process, but they all have one thing in common:

不同的块模式对IV选择过程提出了不同的要求,但它们都有一个共同点:

You must never encrypt two different messages with the same IV and key.If you do, an attacker can usually get your plaintext, and sometimes your key (or equivalently useful data).

您绝不能使用相同的 IV 和密钥加密两个不同的消息。如果这样做,攻击者通常可以获得您的明文,有时还可以获得您的密钥(或等效的有用数据)。

CBC imposes an additional constraint, which is that the IV must be unpredictable to an attacker - so artjom-b's suggestion of using a SecureRandomto generate it is a good one.

CBC 强加了一个额外的限制,即 IV 对攻击者来说必须是不可预测的——所以 artjom-b 使用 aSecureRandom生成它的建议是一个很好的建议。



Additionally, as artjob-b points out, CBC only gives you confidentiality. What that means in practice is that your data is kept secret, but there's no guarantee that it arrives in one piece. Ideally, you should use an authenticatedmode, such as GCM, CCM, or EAX.

此外,正如 artjob-b 指出的那样,CBC 只为您保密。这在实践中意味着您的数据是保密的,但不能保证它是一体到达的。理想情况下,您应该使用经过身份验证的模式,例如 GCM、CCM 或 EAX。

Using one of these modes is a really, really good idea. Encrypt-then-MAC is unwieldy even for the experts; avoid it if you can. (If you have to do it, remember that you mustuse different keys for encryption and MAC.)

使用其中一种模式是一个非常非常好的主意。即使对于专家来说,先加密后 MAC 也是笨拙的;如果可以,请避免它。(如果必须这样做,请记住必须使用不同的密钥进行加密和 MAC。)

回答by Artjom B.

When no IvParameterSpec is provided then the Cipher shouldinitialize a random IV itself, but it seems that in your case, it doesn't do this (new byte[16]is an array filled with 0x00 bytes). It seems the Cipher implementation is broken. In that case you should always provide a new random IV (necessary for semantic security).

当没有提供 IvParameterSpec 时,密码应该初始化一个随机的 IV,但在你的情况下,它似乎没有这样做(new byte[16]是一个填充了 0x00 字节的数组)。似乎密码实现已损坏。在这种情况下,您应该始终提供一个新的随机 IV(语义安全所必需的)。

This is usually done this way:

这通常是这样完成的:

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));

When you then send or store the ciphertext, you should prepend the IV to it. During decryption you only need to slice the IV off the front of the ciphertext to use it. It doesn't need to be kept secret, but it should be unique.

当你发送或存储密文时,你应该在它前面加上 IV。在解密过程中,您只需要从密文前面切下 IV 即可使用它。它不需要保密,但它应该是独一无二的。



Note that CBC mode alone only gives you confidentiality. If any type of manipulation of ciphertexts (malicious or non-malicious) is possible then you should use an authenticated mode like GCM or EAX. Those will also give you integrity in addition to confidentiality. If you don't have access to those (SpongyCastle has them), you could use a message authentication code (MAC) in an encrypt-then-MAC scheme, but it is much harder to implement correctly.

请注意,仅 CBC 模式只能为您提供机密性。如果任何类型的密文操作(恶意或非恶意)都是可能的,那么您应该使用像 GCM 或 EAX 这样的身份验证模式。除了保密性之外,这些还将为您提供完整性。如果您无权访问这些(SpongyCastle 有),您可以在先加密后的 MAC 方案中使用消息身份验证代码 (MAC),但正确实施要困难得多。

回答by Kirill Karmazin

By default when you encrypt - your cipher will generate a random IV. You must use exactly that specific IV when you decrypt that data.

默认情况下,当您加密时 - 您的密码将生成一个随机 IV。当您解密该数据时,您必须完全使用该特定的 IV。

The good news is that IV is not a secret thing - you can store it in public. The main idea is to keep it different for every encrypt-decrypt operation.

好消息是 IV 不是秘密 - 您可以将其存储在公共场合。主要思想是使每个加密 - 解密操作都不同。

Most of the times you will need to encrypt-decrypt various data and storing each IV for each piece of data is a pain. That's why IV is often stored along with the encrypted data in a single string, as a fixed size prefix. So that when you decrypt your string - you definitely know that first 16 bytes (in my case) are your IV, the rest of the bytes - are the encrypted data and you need to decrypt it.

大多数情况下,您需要对各种数据进行加密-解密,并为每条数据存储每个 IV 是一件痛苦的事情。这就是为什么 IV 通常与加密数据一起存储在单个字符串中,作为固定大小的前缀。因此,当您解密字符串时 - 您肯定知道前 16 个字节(在我的情况下)是您的 IV,其余字节是加密数据,您需要对其进行解密。

Your payload (to store or send) will have the following structure:

您的有效负载(用于存储或发送)将具有以下结构:

[{IV fixed length not encrypted}{encrypted data with secret key}]

[{IV fixed length not encrypted}{encrypted data with secret key}]

Let me share my encrypt and decrypt methods, I'm using AES, 256 bit secret key, 16 bit IV, CBC MODE and PKCS7Padding. As Justin King-Lacroix stated above you better use GCM, CCM, or EAX block modes. Do not use ECB!

让我分享我的加密和解密方法,我使用的是 AES、256 位密钥、16 位 IV、CBC 模式和 PKCS7Padding。正如 Justin King-Lacroix 上面所说,您最好使用 GCM、CCM 或 EAX 块模式。不要使用欧洲央行!

Result of encrypt()method is safe & ready to store in DB or send anywhere.

encrypt()方法的结果是安全的,可以存储在数据库中或发送到任何地方。

Notea comment where you can use custom IV - just replace new SecureRandom() with new IvParameterSpec(getIV()) (you can input there your static IV but this is strongly NOTrecommended)

请注意您可以使用自定义 IV 的注释 - 只需将 new SecureRandom() 替换为 new IvParameterSpec(getIV()) (您可以在那里输入您的静态 IV,但强烈推荐这样做)

private Key secretAes256Keyis a class field with a secret key, it is initialized in the constructor.

private Key secretAes256Key是一个带有密钥的类字段,它在构造函数中初始化。

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

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

    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;
}

And here is the decrypt()method:

这是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.

希望这可以帮助。