1

For non-cryptographic purposes (which use a HWRNG instead), I implemented into a bare metal program a Xorshift* RNG (Taken from [1], see below for implementation).

The RNG is currently seeded with the cycle counter and I'd like to XOR in a second cycle counter value once some hardware was initialized that takes a varying amount of time.

I am wondering whether it's ok to:

prng_srand(cycle_counter());
initialize_hardware_and_bind_drivers();
prng_srand(cycle_counter());

Or if I need to do the following instead:

u64 initial = cycle_counter();
prng_srand(initial);
initialize_hardware_and_bind_drivers();
prng_srand(cycle_counter() - initial);

To make the two seedings of the PRNG independent of each other and not impact quality negatively?

Here's the implementation:

static u64 prng_state = 1;
static u32 prng_rand(void)
{
        u64 x = prng_state;
    x ^= x >> 12;
    x ^= x << 25;
    x ^= x >> 27;

    prng_state = x;

    return (x * 0x2545F4914F6CDD1DULL) >> 32;

}

void prng_srand(u64 entropy) { prng_state ^= entropy; /* Ensure prng_state is never zero */ prng_state += !prng_state; prng_rand() }

a3f
  • 13
  • 4

1 Answers1

1

Both methods are of similar quality, because there's a call to prng_rand() [albeit missing a ; in the question] in prng_srand(), which mixes prng_state, thus the similarity of the two values of entropy in the two calls to prng_srand() in the first solution will not have much adverse effect. Specifically, even if a bit does not change in the two entropy, that bit influences at least one bit of the state. If we want to err on the safe side, we can pre-spread the entropy in the second cycle_counter() by multiplying it by some odd constant, which is slightly simpler than the second option, and arguably better than either.

prng_srand(cycle_counter());
initialize_hardware_and_bind_drivers();
prng_srand(cycle_counter()*0x7b9b3a16dbULL); // spread to avoid any partial cancellation

This is far from a cryptographically secure RNG:

  • it's state is only 64-bit;
  • nearly 32-bit worth of that state is leaked by each output, leaving only 32 bits to guess for a brute-force attack;
  • it's linear in seeding and state change, so the effect of seed(s) on the state after any fixed number of steps is equivalent to multiplication of the state (seen as a vector of 64 bits) by a fixed, easily computed matrix.

[on the positive side, the period is 264-1 regardless of seeding; shift counts are chosen for that and good diffusion; and the output is very nearly uniformly distributed].

Most importantly in the context, there is very little insurance about how much entropy this RNG is actually seeded with. That depends on the granularity of cycle_counter(), and on how much what's timed actually varies, which we can't tell. It's very much to fear that on two different boots among a few hundred or thousands, the RNG ends up initialized in the exact same state. That at least used to be a common pitfall, e.g. CVE-2008-0166.

If feasible, I suggest adding the current time (from an RTC) in the seeding material, which will make that issue significantly less likely to cause damage in the context. Other options include some time or mount count in the file system.

fgrieu
  • 149,326
  • 13
  • 324
  • 622