The Java SecureRandom class
The SecureRandom class, housed in the java.security package, provides
a drop-in replacement to java.lang.Random. But unlike the latter,
java.security.SecureRandom is designed to be cryptographically secure.
We'll look in more detail below at what this means. First, let's address the question
from a simple, practical point of view: when should you use SecureRandom?
When to use SecureRandom
SecureRandom is typically used in cases where:
- random numbers are generated for security related purposes,
such as generating an encryption key or session ID (see below);
- or, more generally, high-quality randomness is important and it is
worth consuming CPU (or where CPU consumption isn't an issue)
to generate those high-quality random numbers.
On the other hand, it may not be an appropriate generator where
CPU time need to generate numbers is a factor that is
worth trading for quality of randomness.
As a rough guide:
SecureRandom is about twenty to thirty times slower
than "good medium-quality" generators such as the XORShift or the
Numerical Recipes combined generator.
CPU usage may be an issue, for example, in:
- a simulation or Monte Carlo experiment
where a very large quantity of numbers needs to
be generated quickly;
- where a moderate quantity of numbers needs to be generated
but where there are "better things to spend the CPU time on"
(e.g. a non-gambling game where the quality of randomness isn't crucial);
- a multi-user environment where a moderate quantity of
numbers needs to be generated but with minimal impact on other
processes.
Properties of SecureRandom
We said that SecureRandom is designed to be cryptographically secure.
In practice, this means that the generator has the following properties:
- given only a number produced by the generator, it is (to all intents and purposes)
impossible to predict previous and future numbers;
- the numbers produced contain no known biases;
- the generator has a large period (in Sun's standard implementation,
based on the 160-bit SHA1 hash function, the period is 2160);
- the generator can seed itself at any position within that period
with equal probability (or at least, it comes so close to this goal, that we
have no practical way of telling otherwise).
These properties are important in various security applications. The first is important,
for eaxmple, if we use the generator to produce, say, a session ID
on a web server: we don't want user n to predict user n+1's session ID. Similarly,
we don't want a user in an Internet cafe, based on the session ID or encryption key that they are
given to access a web site, to be able to predict the value assigned to a previous user
on that machine.
The other properties are together important when picking, say, a session ID
or encryption key, to ensure that any of all the possibly values (or a sufficient
proportion of them) can be selected, and we'll take a moment now to consider why.
The importance of producing "all values with equal probability"
For example, let's say that we want to pick a 128-bit AES encryption key.
The idea of a strong encryption algorithm such as AES is that in order for an adversary to guess
the key by "brute force" (which we assume is the only possible means), they would have to
try every single possible key in turn until they hit on the right one. By law of averages,
we would expect them to find it after half as many guesses as there are possible keys.
A 128-bit key has 2128 possible values, so on average, they would have to try
2127 keys.
In decimal 2127 is a 39-digit number. Or put another way,
trying a million million keys per second, it would take 5x1015 millennia
to try 2127 keys. Not even the British government wants to decrypt your party
invitations that badly.
So with current mainstream technology1, a 128-bit key is in principle sufficient for most applications.
But these metrics hold true only if our key selection algorithm— i.e. our random
number generator— genuinely can pick any of the possible keys. For example,
we certainly should not choose the key as follows:
// This is WRONG!! Do not do this!
Random ranGen = new Random();
byte[] aesKey = new byte[16]; // 16 bytes = 128 bits
ranGen.nextBytes(aesKey);
The problem here is that the period of java.util.Random is only 248.
Even though we're generating a 128-bit key, we will only ever pick from a subset of
248 of the possible keys. Or put another way: an attacker need only try
on average 247 keys, and will find our key by trial and error in a couple of days
if they try just a thousand million keys per second.
And as if that wasn't bad enough, they probably don't even need to try anywhere near 247:
for reasons discussed earlier, there's a good chance that an instance of java.util.Random
created within a couple of minutes of bootup will actually be seeded from a about one thousandth
of the 248 possible values.
This time, HM Sniffing Service doesn't
even need expensive hardware to find the secret location of your housewarming party: a trip
to Staples will give them all the computing power they need.
So as you've probably guessed, our solution to the problem is to use SecureRandom
instead:
import java.security.SecureRandom;
..
Random ranGen = new SecureRandom();
byte[] aesKey = new byte[16]; // 16 bytes = 128 bits
ranGen.nextBytes(aesKey);
Now, there's a good chance that any of the 2128 possible
keys will be chosen.
Seeding of SecureRandom
In order to provide this property of choosing any seed with "equal" likelihood,
(or at least, with no bias that is practically detectable), SecureRandom
seeds itself from sources of
entropy available from the local machine, such as timings of I/O events.
1. Note that with a quantum computer, the complexity of guessing
the key is apparently proportional to the square root of the number of possible keys.
Then, guessing the 128-bit key at the same rate would take only about 200 days.
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.