Java 中的 "AES/CBC/pkcs7padding" 向量不变

Vector not changing with "AES/CBC/pkcs7padding" in Java

我在 java 中使用 "AES/CBC/pkcs7padding" 模式在 2 台设备之间进行通信。

建立通信后,两个设备中的一个将分配一个随机IV并将其发送到另一个设备。接收者将使用这个 IV 来实例化一个 encryptionCipher 和一个 decryptionCipher(见下面的代码)。

注意:这里我只放了加密代码,但我们有类似的解密代码。

IV 向量已在通信开始时发送。然后,我们希望这 2 个设备在不发送 IV 的情况下交换加密消息。 如果我们的理解是正确的,只要没有消息丢失,两个设备都应该知道在 XOR 中使用的当前 "vector" 是什么。

然而,Java 代码并没有像我们预期的那样工作: 如果我连续调用 encrypt() 2 次,它会生成相同的加密数据。 这不是我们所期望的,因为我们认为第一次加密的结果将是第二次加密的 "vector"(此向量与明文之间的 XOR,如 https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC 所示)。

我们的理解有误吗?我们在实施过程中遗漏了什么吗?

private Cipher mEncryptionCipher;

private void createEncryptionCipher(byte[] iv) {

    mEncryptionCipher = null;

    try {
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        mEncryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
        mEncryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }

}


private byte[] encrypt(byte[] data) {

    if (mEncryptionCipher == null) {
        Log.e(TAG, "Invalid mEncryptionCipher!");
        return null;
    }

    try {
        int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
        byte[] encodedData = new byte[sizeOfEncryptedData];

        int cipherBytes = mEncryptionCipher.update(data, 0, data.length, encodedData, 0);

        //allways call doFinal
        cipherBytes += mEncryptionCipher.doFinal(encodedData, cipherBytes);

        return encodedData;

    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (ShortBufferException e) {
        e.printStackTrace();
    } catch (STException e) {
        e.printStackTrace();
    }

    return null;
}

正如 Boris the Spider 和 James K Polk 所指出的,doFinal() 会重置密码对象的状态,因此,如果我们进行新的加密,它会从原始 IV 重新开始,这不是 CBC 想要的行为。

我已经尝试了下面的解决方案并且有效。我正在检索最后一个加密块并将其用作下一次加密的 IV。

private byte[] mEncryptionIV = new byte[INITIALIZATION_VECTOR_SIZE];

private byte[] encrypt(byte[] data) {

    try {
        int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
        byte[] encodedData = new byte[sizeOfEncryptedData];

        IvParameterSpec ivParameterSpec = new IvParameterSpec(mEncryptionIV);
        Cipher encryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
        encryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);

        int cipherBytes = encryptionCipher.update(data, 0, data.length, encodedData, 0);

        //always call doFinal
        cipherBytes += encryptionCipher.doFinal(encodedData, cipherBytes);

        // The last encrypted block will be used as IV for the next encryption
        System.arraycopy(encodedData, encodedData.length-AES_BLOCK_LENGTH, mEncryptionIV, 0, mEncryptionIV.length);

        return encodedData;

    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (ShortBufferException e) {
        e.printStackTrace();
    } catch (STException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    }

    return null;
}

正如其他人所建议的那样,尽可能使用 TLS。

否则,至少要吸取TLS设计的教训(错误)。在 TLS 1.0 (https://www.rfc-editor.org/rfc/rfc2246), the CBC cipher state was maintained across separate records, which I think is what you are trying to do across your 'packets'. In TLS 1.1 (https://www.rfc-editor.org/rfc/rfc5246) 中,这已更改,因此每个记录都包含一个显式 IV。每个记录的 IV“应该随机选择,并且必须是不可预测的”。所以每条记录都是独立加密的。

请特别注意 RFC 5246 中安全分析的这一部分:

F.3. Explicit IVs

[CBCATT] describes a chosen plaintext attack on TLS that depends on knowing the IV for a record. Previous versions of TLS [TLS1.0] used
the CBC residue of the previous record as the IV and therefore
enabled this attack. This version uses an explicit IV in order to
protect against this attack.

还有其他陷阱等着你,所以我 return 我的第一个建议:尽可能使用 TLS。

编辑:糟糕,TLS 1.1 RFC 实际上是 https://www.rfc-editor.org/rfc/rfc4346,它具有相同的 F.3 部分。