如果我初始化AES密码,有和没有IvParameterSpec有什么区别吗?

2022-09-02 19:39:54

我想知道,如果我初始化AES密码,有和没有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]));

不带 IvParameterSpec

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

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

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


答案 1

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

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

(维基百科上关于块密码模式 - http://en.wikipedia.org/wiki/Block_cipher_mode - 非常好,并清楚地表明为什么你需要一个IV。

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

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

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


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

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


答案 2

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

好消息是,IV不是一件秘密的事情 - 你可以把它存储在公共场合。主要思想是为每个加密 - 解密操作保持不同。

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

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

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

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

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

请注意一个可以使用自定义IV的注释 - 只需将新的SecureRandom()替换为新的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;
}

希望这有帮助。