Random fork-safety

From OpenSSLWiki
Revision as of 18:44, 25 October 2013 by Jwalton (talk | contribs) (Added note on mobile devices.)

Jump to: navigation, search

One of the most important issues in the proper cryptographic use of random numbers is that random numbers must not be reused. Since the UNIX fork() system call duplicates the entire process state, a random number generator which does not take this issue into account will produce the same sequence of random numbers in both the parent and the child (or in multiple children), leading to cryptographic disaster (i. e. people being able to read your communications).

OpenSSL's default random number generator mixes in the PID, which provides a certain degree of fork safety. However, once the PIDs wrap, new children will start to produce the same random sequence as previous children which had the same PID. This is unlikely to happen in most common cases, but it is not impossible, which makes the issue even more insidious.

The most comprehensive explanation of this problem is probably this blog post:

However, since this issue has been "rediscovered" and discussed multiple times, here are some additional links (some are also linked from the above article)

Remediations

OpenSSL cannot fix the fork-safety problem because its not in a position to do so. However, there are remediations available and they are listed below.

  • Don't use RAND_bytes
  • Call RAND_seed after a fork
  • Call RAND_poll after a fork
  • Use a hardware based generator
  • Practice hedging cryptography

The first remediation is to avoid using RAND_bytes. Instead, you can read directly from /dev/random, /dev/urandom or /dev/srandom; or use CryptGenRandom on Windows systems. Avoiding RAND_bytes is not practical in practice because the library will use it internally.

The second remediation is to call RAND_seed or RAND_add after a fork. Entropy can be obtained from the operating system by reading from /dev/random, /dev/urandom or /dev/srandom; or using CryptGenRandom on Windows systems. For mobile devices with an interactive user, you could even add sensor data from the accelerometer, magnetometer and gyroscopes. This is appropriate for most programs, but might have problems in low entropy environments such as mobile devices and headless servers. Additionally, this could have problems in virtualized environments. For details, see Random Numbers.

The third remediation is to use RAND_poll after a fork. This is used by OpenSSL to seed the generator on startup. The function always calls /dev/urandom, so you will have to seed the generator yourself if you want to use /dev/random or /dev/srandom. This is appropriate for most programs, and has the same potential problems as RAND_seed or RAND_add.

The fourth remediation is to use a hardware based generator. This is not always practical because hardware is not always present. Additionally, hardware is not usually auditable so some question its unabridged use in the post-Snowden era. For details, see Random Numbers and OpenSSL engine(3) man page.

The fifth remediation is to practice hedging cryptography. Hedging uses entropy gathered from a peer during key exchange or key agreement to add to the program's internal entropy pool (for example, the random RA or RB in SSL/TLS). The benefit of hedging is its resilient against fork problems, low entropy environments, and virtual machine playbacks. For details, see When Virtual is Harder than Real: Resource Allocation Challenges in Virtual Machine Based IT Environments and When Good Randomness Goes Bad: Virtual Machine Reset Vulnerabilities and Hedging Deployed Cryptography.

Finally, do not call RAND_cleanup to reset the generator. RAND_cleanup will erase the current state, but it will also set the random method to NULL. That means subsequent calls to RAND_bytes and RAND_pseudo_bytes will fail unless you call RAND_set_rand_method. Below is from rand_lib.c.

void RAND_cleanup(void)
{
    const RAND_METHOD *meth = RAND_get_rand_method();
    if (meth && meth->cleanup)
        meth->cleanup();
    RAND_set_rand_method(NULL);
}

int RAND_bytes(unsigned char *buf, int num)
{
    const RAND_METHOD *meth = RAND_get_rand_method();
    if (meth && meth->bytes)
        return meth->bytes(buf,num);
    return(-1);
}

int RAND_pseudo_bytes(unsigned char *buf, int num)
{
    const RAND_METHOD *meth = RAND_get_rand_method();
    if (meth && meth->pseudorand)
        return meth->pseudorand(buf,num);
    return(-1);
}