1. Overview

In this tutorial, we’ll discuss how to use an Initialization Vector (IV) with encryption algorithms. We’ll also discuss the best practices while using the IV.

This article assumes a basic understanding of cryptography.

We’ll be using the AES algorithm in different modes for all our examples.

2. Encryption Algorithms

Any cryptographic algorithm takes some data or plaintext and a key to produce an encrypted text or ciphertext. And, it also takes the generated ciphertext and the same key to produce the decrypted data or original plaintext back.

For example, the block cipher algorithm provides security by encrypting and decrypting fixed-length blocks. We use different encryption modes to repeatedly apply an algorithm to the entire data and dictate the type of IV to use.

In the case of block ciphers, we work with the same size blocks. If the plaintext size is smaller than the block size, we use padding. Some modes don’t use padding as they use block cipher as a stream cipher.

3. Initialization Vector (IV)

We use an IV in a cryptographic algorithm as a starting state, adding this to a cipher to hide patterns in the encrypted data. This helps avoid the need to re-issue a new key after each invocation.

3.1. Properties of an IV

We use a unique sequence or an IV for most modes of encryption. And, we should never reuse the same IV with the same key. This ensures a distinct ciphertext for the same plaintext encryption, even if we encrypt it multiple times with the same key.

Let’s look at some of the characteristics of an IV, depending on the encryption mode:

  • It has to be non-repeating
  • Based on the encryption mode, it needs to be random also
  • It need not be secret
  • It needs to be a cryptographic nonce
  • The IV of AES is always 128-bit regardless of the key length

3.2. Generating the IV

We can get an IV directly from the Cipher class:

byte[] iv = cipher.getIV();

If we’re unsure of the default implementation, we can always write our method to generate the IV. If we don’t provide an explicit IV, then Cipher.getIV() is used to implicitly get the IV. We can use any method to generate an IV as long as it complies with the properties discussed above.

First, let’s create a random IV by using SecureRandom:

public static IvParameterSpec getIVSecureRandom(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] iv = new byte[Cipher.getInstance(algorithm).getBlockSize()];
    random.nextBytes(iv);
    return new IvParameterSpec(iv);
}

Next, we’ll create the IV by getting the parameters from the Cipher class:

public static IvParameterSpec getIVInternal(Cipher cipher) throws InvalidParameterSpecException {
    AlgorithmParameters params = cipher.getParameters();
    byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
    return new IvParameterSpec(iv);
}

We can use any of the above methods to generate a random, unpredictable IV. However, for some modes like GCM, we use the IV together with a counter. In such cases, we use the first few bytes, mostly 12 for the IV and the next 4 for the counter:

public static byte[] getRandomIVWithSize(int size) {
    byte[] nonce = new byte[size];
    new SecureRandom().nextBytes(nonce);
    return nonce;
}

In this case, we need to ensure that we don’t repeat the counter and that the IV is also unique.

Lastly, though not recommended, we can also use a hardcoded IV.

4. Using IV with Different Modes

As we know, the prime function of encryption is to mask plaintext so that attackers can’t guess it. Therefore, we use different cipher modes to mask the patterns within the ciphertext.

Modes like ECB, CBC, OFB, CFB, CTR, CTS, and XTS provide confidentiality. But these modes don’t protect against tampering and modification. We can add a Message Authentication Code (MAC) or a digital signature for detection. We use various implementations that provide combined modes under authenticated encryption (AE). CCM, GCM, CWC, EAX, IAPM, and OCB are a few examples.

4.1. Electronic Codebook (ECB) Mode

Electronic Codebook mode encrypts each block separately with the key. This always encrypts the identical plaintext into identical ciphertext blocks, thereby not hiding patterns well. Thus, we don’t use it for cryptographic protocols. Decryption is also equally vulnerable to replay attacks.

To encrypt data in ECB mode, we use:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
ciphertext = cipher.doFinal(data);

To decrypt data in ECB mode, we write:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
plaintext = cipher.doFinal(cipherText);

We haven’t used any IV, so the same plaintext will result in the same ciphertext, thus making it vulnerable to attacks. Though ECB mode is the most vulnerable, it’s still the default encryption mode for many providers. So, we need to be more vigilant about explicitly setting the encryption mode.

4.2. Cyber Block Chaining (CBC) Mode

Cyber Block Chaining mode uses an IV to prevent the same plaintexts resulting in the same ciphertext. We need to take care that the IV is reliably random or unique. Otherwise, we’ll have the same vulnerability as ECB Mode.

Let’s get the random IV using getIVSecureRandom:

IvParameterSpec iv = CryptoUtils.getIVSecureRandom("AES");

First, we’ll use the IV to encrypt data using CBC mode:

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

Next, let’s pass the same IV using the IvParameterSpec object for decryption:

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));

4.3. Cyber Feedback (CFB) Mode

Cyber Feedback mode is the most basic streaming mode. It’s like a self-synchronizing stream cipher. Unlike the CBC mode, we don’t need any padding here. In CFB mode, we use IV as the source of the stream generated by the cipher. Again, if we use the same IV for different encryptions, similarities might show up in the ciphertext. Here also, like the CBC mode, IV should be random. In case IV is predictable, then we lose out on confidentiality.

Let’s generate a random IV for CFB mode:

IvParameterSpec iv = CryptoUtils.getIVSecureRandom("AES/CFB/NoPadding");

Another extreme case is if we use an all-zero IV, then under CFB-8 mode, some keys may generate an all-zero IV and all-zero plaintext. In this case, 1/256 keys will generate no encryption. This will result in returning the plaintext as ciphertext.

For CBC and CFB modes, reusing an IV reveals information about the common blocks shared by two messages.

4.4. Counter (CTR) and Output Feedback (OFB) Modes

Counter mode and Output Feedback mode make a block cipher into a synchronous stream cipher. Each mode generates keystream blocks. In this case, we initialize the cipher with a particular IV. We do this primarily to allocate 12 bytes to the IV and 4 bytes to the counter. This way, we can encrypt a message of length 2^32 blocks.

Here, let’s create an IV:

IvParameterSpec ivSpec = CryptoUtils.getIVSecureRandom("AES");

For CTR mode, the initial bitstream is dependent on the IV and key. Here also, reusing an IV will cause key bitstream reuse. This, in turn, will result in breaking the security.

If the IV is not unique, then the counter may fail to provide the expected confidentiality for the blocks that correspond to the repeating counter blocks. However, the other data blocks are not affected.

4.5. Galois/Counter (GCM) Mode

Galois/Counter mode is an AEAD mode of encryption. It combines Counter mode encryption with an authentication mechanism. And, it protects both plaintext and additional authenticated data (AAD).

However, this authentication in GCM depends on the uniqueness of the IVs. We use a nonce as the IV. In case we repeat even one IV, then our implementation may be vulnerable to the attacks.

As GCM uses AES for encryption, the IV or the counter is 16 bytes. Therefore, we use the first 12 bytes as the IV and the last 4 bytes nonce as a counter.

To create an IV in GCM mode, we need to set GCMParameterSpec. Let’s create an IV:

byte[] iv = CryptoUtils.getRandomIVWithSize(12);

First, let’s get an instance of the Cipher and initialize it using the IV:

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));

Now, we’ll create and initialize Cipher with the IV for decryption:

cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));

Here also, we need a unique IV, else one can decipher the plaintext.

4.6. Summing up of IVs

The following table summarizes the type of IV needed for different modes:

Screenshot-2021-10-22-at-10.42.59-AM

As we have seen, reusing an IV with the same key results in loss of security. If possible, we should use more advanced modes like GCM. Also, some modes like CCM are not available in the standard JCE distribution. In this case, we can use Bouncy Castle APIs to implement it.

5. Conclusion

In this article, we showed how to use an IV in different encryption modes. We also discussed the issues and best practices while using an IV.

As always, we can find the source code over on GitHub.