Pseudo-random-number-generators (PRNGs) require a good seed if the sequence of random numbers should be as random as possible. The C++ Standard Template Library (STL) provides std::random_device for using a local entropy source. You cannot easily see what the compiler uses on the target platform. Potential source of entropy are processor commands (RDRAND), keyboard/mouse timings, network events, jitter entropy, or other implementation details. Most operating systems maintain a pool of entropy and feed it into a stream cipher with a random key. The STL random device may return 0 or data not generated by an entropy source. You should check for entropy sources by calling the entropy() member function of the random device instance. If you get a 0 value, then you need to create your own seed value(s). Where to get them?
You may be tempted to use the C++11 std::seed_seq to create a seed sequence, but the sequence generator first needs integer input values. Furthermore, there are some properties of the seed sequence container you should check out before relying on it. The usual approach is to use counters, timestamps of clocks, memory addresses of storage areas (if the operating system’s address randomisation is enabled), or some other local states that change and are hard to predict. If you do that, then you should use multiple sources and mix them further by feeding it to a second PRNG. Splitmix64 or the Xoroshiro128+ family of algorithms is useful because of the low complexity. The Linux® kernel can always help you out with its /dev/urandom pool of entropy. The Microsoft® Windows platform offers the CryptGenRandom() function.
Most of the time the compilers can find a suitable source. Make sure that your compiler does not have bugs regarding the implementation (see MingGW-w64 bug 338 for example). I did some check and found a recent version of GCC on Microsoft® Windows to provide a random device with a constant output. Calling the entropy() member function and having a fallback code for generating the seed is not expensive and does not add much code. Here is an example (needs further testing, of course):
uint32_t get_seed() {
uint32_t s = static_cast( time(0) );
#if defined(__unix__) || defined(__APPLE__)
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
ifstream urandom( "/dev/urandom", ios::in | ios::binary );
do {
urandom.read( reinterpret_cast(&a), sizeof(a) );
urandom.read( reinterpret_cast(&b), sizeof(b) );
urandom.read( reinterpret_cast(&c), sizeof(c) );
urandom.read( reinterpret_cast(&d), sizeof(d) );
} while ( (a*b*c*d) == 0 );
urandom.close();
s = a * b ^ c * d;
#endif
#if defined(_WIN32) || defined(_WIN64)
// Here could be a way to extract / read a random value from
// the Microsoft® Windows run-time environment.
//
// See for example:
// https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
//
HCRYPTPROV hCryptProv;
union {
BYTE pbData[16];
uint32_t a,b,c,d;
} u;
if ( CryptAcquireContext( &hCryptProv, NULL, (LPCWSTR)L"Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) ) {
do {
if ( CryptGenRandom( hCryptProv, 16, u.pbData) ) {
s = u.a * u.b * u.c * u.d;
}
} while ( s == 0 );
CryptReleaseContext( hCryptProv, 0 );
}
#endif
return( s );
}
Leave a Reply