Everything about Java's SecureRandom

November 28, 2018

Introduction

I am happy finally publishing this post, which I want to write since Java 9 is released in 2017. I aim to show you the details of SecureRandom in Java, not from a cryptographer perspective, but to become a well-informed developer. I will also demonstrate you an alternative, or complementary, widely available hardware based solution, Intel® Secure Key.

All I discuss below can be applied to production systems, none of them are experimental or just for testing. However, this post is informative only and I assume no liability. If you need commercial support, please get in touch with me.

Random Number Generation Basics

Random numbers are needed very often, especially now where cryptography is everywhere. A simple example is every time you visit an HTTPS site, many random numbers are needed.

Random numbers should be:

  • statistically independent: meaning a number should not depend on any previous values
  • uniformly distributed: meaning all values should be observed on the long run
  • unpredictable: meaning it should be impossible to predict the future values (forward prediction) and past values (backtracking)

Consider flipping a coin as our random number generator, which will generate a sequence of heads (H) and tails (T).

  • the landing of H and T are (or should be) independent of each other
  • if we flip the coin lots of time, we can see it is uniformly distributed, so the number of heads are more or less as same as the number of tails
  • it is impossible to predict neither past values nor future values from a sequence of heads and tails

We need a large amount of random numbers today either because of cryptography or applications such as gaming and monte carlo simulation, and it is actually very difficult to generate “true random” numbers. True randomness means it is an essential property, thus algorithms, being deterministic in nature, cannot generate true random numbers. True randomness needs a physical phenomena with inherent randomness, such as quantum or thermal phenomena.

The difficulty of this comes from the fact that because we need to measure something from a physical phenomena, it usually takes time to do such measurements and collect the samples. As a result, such (true) random numbers are generally not used directly but used as an entropy source for deterministic random number generation algorithms which are much faster. In order to distinguish two, the output of such an algorithm is called pseudo random numbers.

There are a few practical sources for true randomness. It can be a quantum phenomena, such as shot noise, nuclear decay, photons/optics, or thermal phenomena, such as thermal or avalanche noise. There are many such products that can measure something and gives you true random values, including simple and cheap ones, and I do not mean they are bad. For example, TrueRNG V3 - USB Hardware Random Number Generator measures the avalanche noise generated on a diode. I have used a similar product, Entropy Key, for years without any problem.

Another example, Intel® Secure Key, is a technology where a true randomness source and a measurement system forming a random number generator is integrated into the processor.

Random vs. SecureRandom

Java provides two ways to generate random numbers:

  • java.util.Random which generates pseudo random numbers
  • java.security.SecureRandom which generates cryptographically strong pseudo random numbers

If you look at the Java documentation, you will see Random uses “a 48-bit seed, which is modified using a linear congruential formula” and it is “not cryptographically secure”. This is one of the oldest algorithms to generate random numbers, and basically it does:

$ X_{n+1} = (a X_n + c) \mod m $

where $X_0$ is the seed and its value is a fixed value XORed with System.nanoTime(), and $X_n$ are successive random numbers.

As you might guess, it is not hard to predict the next random number in the sequence if you know the two before. For example with a code like this:

import java.util.Random;

public class DeconstructJavaUtilRandom {

  public static void main(String[] args) {

    final Random random = new Random();

    // we are going to guess what is going to be third random
    final int firstRandom = random.nextInt();
    final int secondRandom = random.nextInt();

    // these are constants from the java.util.Random source code
    // multiplier
    final long a = 0x5DEECE66DL;
    // addend
    final long c = 0xBL;

    // 16 because 48 - 32 = 16
    // java.util.Random generates 48-bit random numbers
    final long startOfSeed = (long)firstRandom << 16;;

    long nextSeed = 0;

    // we do not know the last 16-bits, so we try all
    for (int i = 0; i < 0xFFFF; i++) {

      // this algorithm is same as java.util.Random
      final long nextSeedGuess = ((startOfSeed + i) * a + c) & ((1L << 48) - 1);
      final long secondRandomGuess = (int)(nextSeedGuess >>> 16);

      if (secondRandomGuess == secondRandom) {
        nextSeed = nextSeedGuess;
        break;
      }

    }

    if (nextSeed != 0) {

      System.out.println("seed found");

      // this algorithm is same as java.util.Random
      nextSeed = (nextSeed * a + c) & ((1L << 48) - 1);
      final int thirdRandomGuess = (int)(nextSeed >>> 16);

      final int thirdRandom = random.nextInt();

      if (thirdRandomGuess == thirdRandom) {

        System.out.println("guessed third random correctly");

      } else {

        System.out.println("wrong third random guess");

      }

    } else {

      System.out.println("seed not found");

    }

  }

}

using Java 8, the above code displays:

seed found
guessed third random correctly

This is a no-no for cryptography, since, for example, we can generate two keys, and we expect knowing one will not make it possible to guess the second. For cryptographically strong random number generation, there are different algorithms and java.security.SecureRandom uses such methods.

This may not be obvious but actually Random and SecureRandom are implemented very differently, in the sense that Random is a concrete class, implemented as I described above, on the other hand, SecureRandom (which extends Random) uses different providers, instances of SecureRandomSpi, for the actual implementation of such algorithms. Thus, saying that you are using SecureRandom may not be enough since it is not clear what algorithm you are actually using.

Available SecureRandom algorithms can be listed with a code like this:

import java.util.Set;
import java.security.Security;
import java.security.SecureRandom;

public class ListSecureRandomAlgorithms {

  public static void main(String[] args) {

    final Set<String> algorithms = Security.getAlgorithms("SecureRandom");

    for (String algorithm : algorithms) {
      System.out.println(algorithm);
    }

    final String defaultAlgorithm = new SecureRandom().getAlgorithm();

    System.out.println("default: " + defaultAlgorithm);

  }

}

using Java 8 on Linux, the code above displays:

SHA1PRNG
NATIVEPRNGBLOCKING
NATIVEPRNGNONBLOCKING
NATIVEPRNG
default: NativePRNG

So the default seems to be NativePRNG, however this is platform dependent and configured in java.security file. The actual selection is (from the java.security.SecureRandom source code):

    /**
     * Gets a default PRNG algorithm by looking through all registered
     * providers. Returns the first PRNG algorithm of the first provider that
     * has registered a SecureRandom implementation, or null if none of the
     * registered providers supplies a SecureRandom implementation.
     */
    private static String getPrngAlgorithm() {
        for (Provider p : Providers.getProviderList().providers()) {
            for (Service s : p.getServices()) {
                if (s.getType().equals("SecureRandom")) {
                    return s.getAlgorithm();
                }
            }
        }
        return null;
    }

On the java.security file, I have these two entries:

security.provider.1=sun.security.provider.Sun
securerandom.source=file:/dev/random

and sun.security.provider.Sun registers implementations through SunEntries class which contains this code:

// register the native PRNG, if available
// if user selected /dev/urandom, we put it before SHA1PRNG,
// otherwise after it
boolean nativeAvailable = NativePRNG.isAvailable();
boolean useNativePRNG = seedSource.equals(URL_DEV_URANDOM) ||
    seedSource.equals(URL_DEV_RANDOM);

if (nativeAvailable && useNativePRNG) {
    map.put("SecureRandom.NativePRNG",
        "sun.security.provider.NativePRNG");
}
map.put("SecureRandom.SHA1PRNG",
     "sun.security.provider.SecureRandom");
if (nativeAvailable && !useNativePRNG) {
    map.put("SecureRandom.NativePRNG",
        "sun.security.provider.NativePRNG");
}

if (NativePRNG.Blocking.isAvailable()) {
    map.put("SecureRandom.NativePRNGBlocking",
        "sun.security.provider.NativePRNG$Blocking");
}

if (NativePRNG.NonBlocking.isAvailable()) {
    map.put("SecureRandom.NativePRNGNonBlocking",
        "sun.security.provider.NativePRNG$NonBlocking");
}

As you expect, NativePRNG is platform specific:

  • For Solaris/Linux/MacOS, it obtains seed and random numbers from /dev/random and /dev/urandom and reads securerandom.source Security property and java.security.egd System property. The default is to obtain seed from /dev/random and obtain random numbers from /dev/urandom.

  • For Windows, NativePRNG is not implemented, but Windows native implemetation is provided using SunMSCAPI provider.

If you do not know the difference between /dev/random and /dev/urandom, now it is time to learn. /dev/random is a full entropy source, meaning it only gives you truly random numbers read from physical phenomena such as your mouse movements. On the other hand, /dev/urandom is the output of a random number generator. The important thing is generation of true random numbers is a slow process. If you do a cat /dev/random, it is going to display some but then block, you can wait or for example move the mouse and you will see more data coming. Of course, this is an unacceptable situation for some/many cases, so /dev/urandom is provided for this reason. This behavior can be improved/modified, for example by installing rng-tools.

So the NativePRNG actually comes in three variants.

NativePRNG VariantSeed SourcePseudo Random Source
NativePRNG (MIXED)/dev/random/dev/urandom
NativePRNG.Blocking/dev/random/dev/random
NativePRNG.NonBlocking/dev/urandom/dev/urandom

We also have SHA1PRNG. It is selected, when no PRNG algorithm is available, or based on the code above, when NativePRNG is not available. SHA1PRNG is a pure Java implementation, that may or may not use /dev/[u]random, depending on the java.security.egd System property or securerandom.source Security property.

So before Java 9, your options for SecureRandom are:

  • use platform features, by using NativePRNG on Linux or corresponding implementation on Windows
  • use SHA1PRNG, and optionally use platform as entropy source, for seeding the algorithm

With Java 9, we now have another option, but before that we need to talk about a document.

NIST Special Publication 800-90A Rev. 1

A standard document in this topic is NIST SP 800-90A Rev. 1:Recommendation for Random Number Generation Using Deterministic Random Bit Generators, published in 2012 and updated in 2015. This publication describes three deterministic methods for creating a Deterministic Random Bit Generator (DRBG):

  • Hash Function based: Hash_DRBG and HMAC_DRBG
  • Block Cipher based: CTR_DRBG

It is important to know that you need a randomness source to initialize/seed the DRBG method, thus a DRBG is actually composed of a DRBG method and a randomness source.

As the name indicates, a DRBG is initializied with a seed from a randomness source, and then generates random bits with an algorithm, so it is a deterministic process. The methods defined in this document produces cryptographically secure (pseudo) random bits / numbers. However, the actual properties of a DRBG depends on the randomness source as well.

Java 9 brings exactly this to Java.

JEP 273: DRBG-Based SecureRandom Implementations

Java 9 introduces JEP 273, which contains the implementation of the three DRBG methods described in NIST SP 800-90A Rev. 1 publication.

Like above, if we look at the SecureRandom algorithms on Java 9:

jshell> java.security.Security.getAlgorithms("SecureRandom")
$1 ==> [DRBG, SHA1PRNG, NATIVEPRNGBLOCKING, NATIVEPRNGNONBLOCKING, NATIVEPRNG]

As you see, there is a new one called DRBG.

If you want to use it, you have to instantiate SecureRandom class by providing the algorithm:

jshell> java.security.SecureRandom drbg = java.security.SecureRandom.getInstance("DRBG")
drbg ==> Hash_DRBG,SHA-256,128,reseed_only

We did not specify the details, but it selected Hash_DRBG.

If you look at the SunEntries on Java 9, you will see the following lines:

map.put("SecureRandom.DRBG", "sun.security.provider.DRBG");
map.put("SecureRandom.DRBG ThreadSafe", "true");

There is a new section in java.security file regarding to DRBG and securerandom.drbg.config Security parameter, and it says:

## The default value is an empty string, which is equivalent to
##   securerandom.drbg.config=Hash_DRBG,SHA-256,128,none
##
securerandom.drbg.config=

With this property, basically you can:

  • select the mechanism: Hash_DRBG, HMAC_DRBG, CTR_DRBG
  • select the Hash function for Hash_DRBG and HMAC_DRBG: SHA-224, SHA-512 / 224, SHA-256, SHA-512 / 256, SHA-384, SHA-512
  • select the Cipher for CTR_DRBG: AES-128, AES-192, AES-256
  • select the security strength: 112, 128, 192, 256
  • specify if prediction resistance or reseeding is needed: pr_and_reseed, reseed_only, none
  • specify if derivation function should be used or not for CTR_DRBG: use_df, no_df

For example, these are valid configurations:

  • Hash_DRBG,SHA-224,112,none
  • CTR_DRBG,AES-256,192,pr_and_reseed,use_df

sun.security.provider.DRBG is the SecureRandom provider for DRBG algorithms, and actual implementations are provided in sun.security.provider.[HashDrbg, HmacDrbg, CtrDrbg] classes.

The DRBG implementation in Java 9 uses the SeedGenerator as entropy input, which reads the entropy from either java.security.egd System property or from securerandom.source Security property. So by default it uses /dev/random in Linux.

What DRBG Config Should I Use ?

This of course depends on the application, but there are a few guidelines:

  • The security strength should be equal to maximum strength you need. For example, if you are going to use 256-bit nonce values directly from the DRBG, you should configure DRBG as 256-bit strength. The concatenation of (for example 128-bit) output does not give a higher (for example 256-bit) strength.

  • The required security strength constraints the selection of hash function or cipher. You can check this in NIST SP 800-57. A very short summary is:

    • for Hash_DRBG and HMAC_DRBG, it is the output length of the hash function, so you cannot select SHA-1 for 256-bits strength, since the output is 160-bits only.
    • for CTR_DRBG, it is the key size of AES used. So you should use AES-256 for 256-bits strength.
  • Reseeding is a good practice if you are going to keep the same DRBG instance for a long time. It basically re-initializes the DRBG, so any risk of leaking the seed or state of DRBG is eliminated.

  • When possible, it is recommended the prediction resistance is requested, and prediction resistance requires reseeding. That means, when possible, pr_and_reseed should be used.

  • The use of derivation function is optional if an approved entropy source (compliant to SP800-90B or SP800-90C) is used. Otherwise, it should be used. A derivation function is always used with Hash_DRBG and HMAC_DRBG, so this parameter (use_df) is only meaningful for CTR_DRBG.

I am not 100% sure why “Hash_DRBG,SHA-256,128,none” is selected as default, I would add pr_and_reseed to that. I guess one possible reason is prediction resistance require feeding DRBG with new seed often, and default entropy sources like /dev/random does not have such a performance. So I think the following points are the most important ones to remember for Java DRBG config:

  • Try to find a SP800-90B or SP800-90C approved entropy source, and provide it with System or Security property.
  • If you can find such an entropy source, request pr_and_reseed also.
  • Do not forget to increase default security strength if you need a higher one.

Some Recommendations

Here are some advices, with no liability assumed.

SHA1PRNG is an old method, unless there is an obvious reason or unless you definitely know what you are doing, it should not be used in production.

If you do not need a standard based approach, you may continue using NativePRNG. Be aware that the security and performance of this approach totally depends on your platform, and usually the details are hidden or hard to know. If you need compliance or total control of the process, I do not recommend this approach.

If you need a standard based approach, for compliance or just to be on the safe side, use the new DRBG methods and use a proper source of entropy.

Be aware that the source of entropy is not specified by Java, and the properties of DRBG is dependent on it. The easiest option is to use the platform entropy source, such as /dev/random. Under certain conditions the platform default entropy source is not enough for variety of reasons, then you should look for another solution. Keep in mind that only approved randomness sources in terms of SP800-90A are the ones conforming to SP800-90B or SP800-90C.

Intel® Secure Key Demo

This section actually has no direct relation with Java, but I will show how it can be used with Java.

Please refer to Intel® Digital Random Generator (DRNG) Software Implementation Guide for more information

Intel® has an on-die (inside the processor) (Digital) Random Number Generator (DRNG) implementation called Intel® Secure Key.

For example, this is the specs of the processor I am using, you can see the line with Secure Key.

This DRNG provides a very simple software interface with two instructions:

  • RDSEED: samples the output of an enhanced nondeterministic random number generator (ENRNG) compliant with SP800-90B/C
  • RDRAND: samples the output of a deterministic random number generator (DRNG) compliant with SP800-90A using CTR_DRBG with reseeding (I am not sure if prediction resistance or derivation function is used, I did not see it in the documents)
Intel Secure Key DRNG Design (from DRNG Software Implementation Guide)

Intel Secure Key DRNG Design (from DRNG Software Implementation Guide)

Both of the instructions can sample a 16, 32 or 64-bits value from the outputs of random number generators, and store it into a register. After RDSEED and RDRAND, the carry flag (CF) should be checked to see if the value on the register is valid.

Because this DRNG is implemented inside the processor, and the entropy source has a high throughput, DRNG has a very high throughput.

A low level implementation can use the instructions directly. However, it is much easier if you have a source that you can feed to applications. For that reason, I made a simple RDRAND/RDSEED character device driver for Linux, called Clarona, that can be easily used by the applications.

It is very simple to use, assuming you have the necessary build environment:

\$ git clone https://github.com/metebalci/clarona.git
\$ cd clarona
\$ make
\$ sudo insmod clarona.ko
\$ dmesg | grep clarona
[ 5568.445987] clarona: Initializing...
[ 5568.445988] clarona: rdseed available: yes
[ 5568.445988] clarona: rdrand available: yes
[ 5568.445990] clarona: Registered with major number: 235
[ 5568.446070] clarona: Class registered correctly
[ 5568.446181] clarona: rdseed device created (235,1)
[ 5568.446267] clarona: rdrand device created (235,2)
[ 5568.446268] clarona: sizeof(unsigned long)=8
[ 5568.446268] clarona: 8 is optimum and good for operation
[ 5568.446268] clarona: Initialization done.
\$ ls -l /dev/clarona-rd*
crw------- 1 root root 235, 2 Nov 29 11:13 /dev/clarona-rdrand
crw------- 1 root root 235, 1 Nov 29 11:13 /dev/clarona-rdseed
\$ sudo chmod 666 /dev/clarona-rd*
\$ dd if=/dev/clarona-rdseed of=out bs=100 count=1MB
dd: warning: partial read (8 bytes); suggest iflag=fullblock
0+1000000 records in
0+1000000 records out
8000000 bytes (8.0 MB, 7.6 MiB) copied, 1.40525 s, 5.7 MB/s

A very high throughput (~ 50MBit/sec) can be seen for the full entropy source (RDSEED).

A theoretical maximum is given in the official documentation as maximum of 100M RDRAND instruction per second. Since output can be 64-bits, 800MB/s is a possibility and the upper bound. It is mentioned that a single thread may see 70 to 200 MB/sec.

Now, it is not difficult to use these in Java, it is not different than /dev/random. A simple example is what I provide in the repo, Example.java which generates 1M x 8bytes seeds.

I am using generateSeed method in Example.java, the reason is NativePRNG MIXED mode uses the entropy source that can be modified only for that method, and uses /dev/urandom for other generate methods.

Using the default, /dev/random:

\$ time java Example NativePRNG
^C
real  0m7.215s
user  0m0.058s
sys   0m0.004s

I waited some seconds but it did not terminate, as expected, because /dev/random cannot provide this much entropy quickly. On the other hand, if we run it by changing the entropy source:

\$ time java -Djava.security.egd="file:///dev/clarona-rdseed" Example NativePRNG

real  0m0.623s
user  0m0.264s
sys	  0m0.380s

finishes under 1 second.