Search code examples
javaencryptioncryptographyaes

AES/CBC/PKCS5Padding encryption with fixed IV (or without one)


I need to code a Java function that encrypts a string using AES256, with CBC mode and PKCS#5 padding. I've been given a secret key and some before-after examples so I can validate my implementation. This is all I've got. I found that the expected results are exactly the one produced by this online generator: https://encode-decode.com/aes-256-cbc-encrypt-online/

One of the parameters I must provide my Cipher instance with is an initialization vector ("IV"). If I don't specify one, Java uses a random one, and therefore produces a different result on each run, which is not the behavior I want.

The above generator does not ask its users for an IV, and still it produces the same results as my target. So I'd like to know how it is possible. Do people tend to use the same IV (regardless of whether it's secure or not), something like "0000000000000000", "1234567812345678" (I tried both, just in case)? Or is there any other way to encrypt with the above parameters without using an IV?

Just in case, here is my code for the moment :

package com.example.test;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesTest {
    public static String key = "abcdefghijklmnopqrstuvwxyz012345";
    public static String email = "[email protected]";
    public static String initVector = "????????????????";

    public static void main(String []args) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");

            cipher.init(
                Cipher.ENCRYPT_MODE,
                new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"),
                new IvParameterSpec(initVector.getBytes(StandardCharsets.UTF_8))
            );

            byte[] encrypted = cipher.doFinal(email.getBytes());
            System.out.println(Base64.getEncoder().encodeToString(encrypted));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Solution

  • Yes, often an all zero IV is often used as default if the IV has not been explicitly defined within a protocol. The initialization vector is XOR'ed with the first plaintext block for CBC, so if all bytes are set to value zero then the plaintext is simply kept.

    Beware that reusing an IV for a key will leak which initial blocks of plaintext are identical to anyone being able to eavesdrop the ciphertext messages. So static IV's are insecure in most situations.

    If decryption with an all zero (or static) IV results in the first block being randomized while the rest contains the full message then the first block of ciphertext was actually the IV - see the following section.

    If only the first block is successfully decrypted then maybe EBC mode was used instead.

    For Java you can just create an IvParameterSpec like this:

    new IvParameterSpec(new byte[cipher.getBlockSize()]):
    

    if the cipher is not yet available then you might just define the AES block size as 16 bytes.

    private static final int AES_BLOCKSIZE = 16;
    

    Library implementations of CBC mode often randomize the IV by default. It is commonplace to prefix the 16 byte IV to the ciphertext - generally this needs to be programmed explicitly by the user of the library.

    CBC only provides semantic security if the IV is fully unpredictable to an adversary, i.e. all bits in the IV must appear random to an adversary. This means that the IV must be randomized which can be accomplished by the random IV, or by e.g. encrypting a nonce and using that as an IV.

    If you try to decrypt the ciphertext using the first block and the first block of plaintext is missing then the IV was either static or calculated differently.