如果我使用和不使用IvParameterSpec启动AES密码,是否有任何区别

时间:2015-04-11 06:50:18

标签: java android security encryption aes

我想知道,如果我使用和不使用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);

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

然而,由于我不是安全专家,我不想错过任何东西,并创建一个潜在的安全循环漏洞。我想知道,这是正确的方法吗?

3 个答案:

答案 0 :(得分:16)

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

  • AES是块密码,一种在128位块上运行的加密算法。
  • CBC是一种分组密码模式,一种使用分组密码加密大量数据的方法。
  • 分组密码模式需要初始化向量(IV),这是一个初始化数据块,通常与底层密码的块大小相同。

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

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

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

CBC强加了一个额外的约束,即IV必须对攻击者不可预测 - 因此artjom-b建议使用SecureRandom生成它是一个很好的约束。


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

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

答案 1 :(得分:4)

当没有提供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有它们),你可以在encrypt-then-MAC方案中使用消息认证码(MAC),但实现起来要困难得多。

答案 2 :(得分:2)

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

好消息是IV并不是秘密的事情-您可以将其公开存储。主要思想是使每个加密/解密操作都保持不同。

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

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

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

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

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

注意一条注释,您可以在其中使用自定义IV-只需将新的SecureRandom()替换为新的IvParameterSpec(getIV())(您可以在此处输入您的静态IV,但这很推荐)

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

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

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

希望这会有所帮助。