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.