6

For unit tests of a cryptographic utility, I would like to be able to force OpenSSL's cryptographic random number generator (both RAND_bytes and RAND_pseudo_bytes) to return predictable, repeatable byte sequences, so that various ciphertexts are in turn predictable and can be baked into test vectors. (All other key material is under my control.)

I know this totally defeats security. This will only be used for unit tests.

I cannot simply call RAND_seed with a fixed seed before each test, because (it appears) the RNG automatically seeds itself from /dev/urandom whether I want it to or not, and anyway RAND_seed doesn't reset the RNG, it only adds the seed to the entropy pool.

Is there any way to do this? (In extremis, it looks like I could write my own PRNG engine, but I'd like to think there's a simpler option.)

jww
  • 97,681
  • 90
  • 411
  • 885
zwol
  • 135,547
  • 38
  • 252
  • 361

2 Answers2

8

You can force the FIPS ANSI X9.31 RNG into a test mode at runtime, but not the SSLeay RNG (the default). If you recompile OpenSSL with -DPREDICT, the default RNG will output a predictable sequence of numbers, but that's not very convenient.

The RAND_pseudo_bytes function generates a predictable series of numbers, meaning it does not add entropy to itself automatically like RAND_bytes. But like you noticed it's only possible to add entropy to the seed, not provide the seed explicitly, so between runs of the program you'll get different numbers. Also not helpful.

But writing your own predictable RNG engine is not difficult. In fact, I'll take you through it by making a rand engine with stdlib's rand() at its core:

#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <openssl/rand.h>

// These don't need to do anything if you don't have anything for them to do.
static void stdlib_rand_cleanup() {}
static void stdlib_rand_add(const void *buf, int num, double add_entropy) {}
static int stdlib_rand_status() { return 1; }

// Seed the RNG.  srand() takes an unsigned int, so we just use the first
// sizeof(unsigned int) bytes in the buffer to seed the RNG.
static void stdlib_rand_seed(const void *buf, int num)
{
        assert(num >= sizeof(unsigned int));
        srand( *((unsigned int *) buf) );
}

// Fill the buffer with random bytes.  For each byte in the buffer, we generate
// a random number and clamp it to the range of a byte, 0-255.
static int stdlib_rand_bytes(unsigned char *buf, int num)
{
        for( int index = 0; index < num; ++index )
        {
                buf[index] = rand() % 256;
        }
        return 1;
}

// Create the table that will link OpenSSL's rand API to our functions.
RAND_METHOD stdlib_rand_meth = {
        stdlib_rand_seed,
        stdlib_rand_bytes,
        stdlib_rand_cleanup,
        stdlib_rand_add,
        stdlib_rand_bytes,
        stdlib_rand_status
};

// This is a public-scope accessor method for our table.
RAND_METHOD *RAND_stdlib() { return &stdlib_rand_meth; }

int main()
{
        // If we're in test mode, tell OpenSSL to use our special RNG.  If we
        // don't call this function, OpenSSL uses the SSLeay RNG.
        int test_mode = 1;
        if( test_mode )
        {
                RAND_set_rand_method(RAND_stdlib());
        }

        unsigned int seed = 0x00beef00;
        unsigned int rnum[5];

        RAND_seed(&seed, sizeof(seed));
        RAND_bytes((unsigned char *)&rnum[0], sizeof(rnum));
        printf("%u %u %u %u %u\n", rnum[0], rnum[1], rnum[2], rnum[3], rnum[4]);

        return 0;
}

Every time you run this program, it seeds srand() with the same number and therefore gives you the same sequence of random numbers every time.

corruptor:scratch indiv$ g++ rand.cpp -o r -lcrypto -g
corruptor:scratch indiv$ ./r
1547399009 981369121 2368920148 925292993 788088604
corruptor:scratch indiv$ ./r
1547399009 981369121 2368920148 925292993 788088604
corruptor:scratch indiv$ 
indiv
  • 17,306
  • 6
  • 61
  • 82
  • Thanks! The OpenSSL documentation gave me the impression that overriding the internal RNG was much harder than that, which is why I was so reluctant to do it. – zwol Sep 22 '11 at 17:42
  • The documentation also strongly implies that one should do this with ENGINEs rather than with RAND_METHODs, but I can't figure out how to implement my own ENGINE. Can you comment on that? – zwol Sep 22 '11 at 17:51
  • @Zack: An ENGINE is a container and it exposes an interface for loading and releasing algorithm implementations dynamically. That's it. So if you were to implement an RNG engine as an ENGINE object, you'd first implement what I showed you above, and then you would implement an ENGINE wrapper around it to get all the benefits of the ENGINE interface. The algorithm interface remains unchanged (the `RAND_*` functions and the `RAND_METHOD` table). If you implement an ENGINE, you should still support OpenSSL users who compiled with `-DOPENSSL_NO_ENGINE` by still providing the table accessor. – indiv Sep 22 '11 at 20:23
1

Write a wrapper around the library. Then substitute it at test time for your own mock that returns your magical values.

Remember, in a unit test you're not trying to test OpenSSL. You're trying to test your code.

John Deters
  • 4,295
  • 25
  • 41
  • This would be as difficult as writing my own PRNG engine for OpenSSL. Getting OpenSSL's PRNG to do what I want _ought to_ be much easier. – zwol Sep 15 '11 at 21:42
  • Alright, then trick the test project's linker. Implement your own RAND_bytes() and RAND_pseudo_bytes() in a source module and it will get linked before any libraries are brought in. – John Deters Sep 15 '11 at 22:00
  • "Implement your own [cryptographic PRNG]" is the thing that is as hard as writing my own ENGINE. The point of the question is that I don't want to have to do that if it can be avoided. – zwol Sep 15 '11 at 23:04
  • 1
    Sorry, I wasn't clear. Your own RAND_bytes() that your test project links to would not be a real random engine. It would be a simple mock implementation to return static test data, something like: RAND_bytes(unsigned char* buf, int num){memcpy(buf, testArray[offset], num); offset+=num; return 1;}. You'd have to pre-create all the random data in testArray, and you'd want some error handling in there, too. But you asked for a way to return non-random data instead of random data, and this is one way to do it. – John Deters Sep 16 '11 at 14:07