The Initialisation Vector (IV)

We saw that in various block modes the essential idea is that for each block, we add in the result of encrypting the previous block. But what happens where there's no previous block— in other words, for the first block of the set of blocks we're encrypting in a given run? Well, the answer turns out to be quite simple: we just supply some initial data. This initial data is called the initialisation vector.

Let's look at a concrete example. It'll come as no surprise that in this example, Alice wants to send some data to Bob. We assume that they've already agreed on a private key, by means that we'll come back to later, and they've agreed on what algorithm and block mode to use: in this case, largely for the sake of argument, let's say it's AES in Counter (CTR) mode. And, importantly, we assume that they've already been sending data to one another using that private key. Now, Alice is ready to send some more data (i.e. a number of blocks of data). So she needs to decide on the Initialisation Vector, which in this case, is simply the initial counter of her Cipher. So what initial counter does she use?

Well, what we do know is that she cannot start at zero. We've already said that it is crucial that we never re-use the same counter value with a given private key. Since Alice and Bob have already been having conversations with this private key, if they started at zero each time, they'd just continually use the same counter range, and for security that would be disastrous.

A better solution would be to use a random value, taken from a high-quality random number generator. It's important that the random numbers are of high quality to mimimise the risk of collisions: that is, where you accidentally re-use a range of counter values that (at least partially) overlap with a previously-used range. If you use a good-quality generator (Java's SecureRandom is adequate, though ideally you should use a separate instance to that used to generate the encryption key), then the risk of collisions remains roughly the same as the risk of hitting a cycle in OFB mode, as discussed on the previous page.

Another solution is to use a global message/block counter to generate the IV. If your message counter goes up by one each time, then you could initialise a 128-bit IV to have the first 64 bits as the message counter, and the next 64 bits as zero (this number will then be incremented on every block):

public byte[] getIV(long messageNo) {
  ByteBuffer bb = ByteBuffer.allocate(16);
  bb.putLong(0, messageNo);
  return bb.array();
}

This IV would be compatible with Sun's implementation of CTR mode, where the counter is big endian (i.e. the highest byte of the IV actually represents the lowest-order part of the counter, and is the "part that incremenst first"). The advantage of this solution is that in many cases, you may be keeping a message counter anyway. The potential disavantage is that you have to be careful to synchronize the counter across all conversations that will be using the same key.

Using a counter-based IV in CBC mode

We need to beware of a potential security problem if we use a message number or other trivially-incrementing value as the initialisation vector in CBC mode:

In CBC mode, the initialisation vector must be randomised; never use a counter or trivially changing value directly as the IV.

So why not? Well, let's consider again how the first block is encrypted in CBC mode. We take the initialisation vector and XOR it with the first block of plaintext. Then that block gets encrypted. If you think about a typical "conversation" between a client and a server, the initial part of the message may differ very trivially. There's a risk that the trivial difference in the initial block of plaintext and the trivial difference in the initial IVs could cancel each other out, and the initial encrypted blocks of various conversations end up identical. This of course potentially leaks information to an attacker.

So if we base the IV on a counter or message number in CBC mode, we must always run it through a hash function (or even simply encrypt it using the private key) so that subsequent IVs do not differ trivially from one another.

How does Alice send the IV to Bob?

Alice and Bob's ciphers must both be initialised with the same IV, or they'll be "out of synch", and Bob won't be able to decrypt Alice's messages. So assuming that Alice decides on a random IV, how does she communicate that IV securely to Bob? Well, the answer is maybe a little surprising: she actually doesn't bother sending it securely, and just sends it unencrypted. In general:

The IV does not need to be kept secret. It can be transmitted in the clear between the two parties, or can be based on information (such as message number) which isn't a secret to an attacker.

This may sound surprising, especially in OFB and CTR modes, where the plaintext is XORed with the encrypted IV. But remember, an attacker cannot determine what the encrypted IV looks like without knowing the key, so they actually have no way to know what has been XORed with the first block of plaintext.

Next: using block modes and initialisation vectors in Java

On the next page, we put our theory into practice, and look at using different block modes in Java.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.