Seeding random number generators (ctd):
looking for entropy

On the previous page, we introduced the problem of finding a random number generator seed. For casual applications, System.nanoTime() will generally return a value that has enough unpredictable bits to seed a generator such as java.util.Random or even the XORShift generator we also mentioned. Using System.nanoTime() almost certainly won't be able to produce any of the 264-1 starting values for the 64-bit XORShift generator, and almost certainly not with equal probability. But for a casual game, it may be good enough.

For more serious applications, we need to find more bits of "true randomness" or entopy. In a moment, we'll see that:

In Java, a practical way to access entropy is to use the SecureRandom class. In some cases we may be able to mix in extra entropy if it is available.

Using SecureRandom to read OS-provided entropy

In practice, modern operating systems help us solve the problem of obtaining entropy. Depending on the machine and environment, there are various sources of entropy potentially available to software. The OS generally "knows" where it can collect entropy from. So it collects entropy "as it goes along" and exposes it to the programmar via an API call or similar. On Windows, this is generally the CryptGenRandom() API call; on UNIX-like systems such as Linux, Solaris and Mac OS, by reading from the /dev/random device file.

In Java, the SecureRandom class then acts as a wrapper to this OS-provided entropy:

To obtain a series of bytes of entropy, we call SecureRandom.getSeed(), having first created an instance of SecureRandom. By default, at least in Sun's implementation, SecureRandom.generateSeed() is effectively a wrapper around the natively available source of entropy, if one is available. If the generator we are seeding allows us to use the byte array directly, then we're home and dry. If the generator requires, say, a long value as its seed, we need to do a bit of conversion. In this case, we can use a ByteBuffer to help us:

public long getLongSeed() {
  SecureRandom sec = new SecureRandom();
  byte[] sbuf = sec.generateSeed(8);
  ByteBuffer bb = ByteBuffer.wrap(sbuf);
  return bb.getLong();
}
..
long seed = getLongSeed();
Random ranGen = new XORShiftRandom(seed);

When should you use SecureRandom.generateSeed()?

In general, use SecureRandom.generateSeed() to seed "non-casual" random number generators where it's important that the generator's starting point is any of the possible values. Typical applications are cryptography, simulations (and other scientific uses), or other applications where high-quality randomness is important, such as gambling games where money is involved.

In general, you would use SecureRandom.generateSeed() in conjunction with a high-quality generator. In fact, our example of using it to seed a generator with a 264-1 period is a little unrealistic. If we're happy with an XORShift generator, we'll generally be happy seeding it from System.nanoTime().

The following should be borne in mind:

Usually these constraints aren't too much of a problem: a single high-quality generator seeded with a few bytes of entropy should last the lifetime of a typical application.

Next: more about SecureRandom

On the next page, we look at the SecureRandom class for random number generation, and typical cases of when to use it.


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.