Search code examples
javaaesbouncycastlecbc-mode

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


I'm using the mode "AES/CBC/pkcs7padding" in java to communicate between 2 devices.

When the communication is setup, one of the two devices will allocate a random IV and send it to the other device. The recipient will use this IV to instantiate an encryptionCipher and a decryptionCipher (see code below).

NB: Here I'm putting only the code for encryption but we have similar code for decryption.

The IV vector has been sent at the beginning of the communication. We then want the 2 devices to exchange encrypted messages without sending the IV anymore. If our understanding is correct, as long as no message is lost, both device should know what is the current "vector" to use in the XOR.

However, the Java code is not working as we expecting: If I call encrypt() 2 consecutive times, it produces the same encrypted data. This is not what we expected because we thought that the result of the first encryption would be the "vector" of the second encryption (XOR between this vector and the plainText as indicated on https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC).

Is our understanding wrong? Did we miss something in the implementation?

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

Solution

  • As others have suggested, just use TLS if at all possible.

    Otherwise, at least take a lesson from TLS' design (mistakes). In 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) this was changed so that each record contained an explicit IV. Each record's IV "SHOULD be chosen at random, and MUST be unpredictable". So each record is encrypted independently.

    Please note in particular this section of the security analysis in 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.

    There are other pitfalls awaiting you too, so I return to my first suggestion: use TLS if at all possible.

    EDIT: Oops, TLS 1.1 RFC is actually https://www.rfc-editor.org/rfc/rfc4346, which has the same F.3 section.