Skip to content

Commit 9d7032e

Browse files
committed
Switch all RNG code to the built-in PRNG.
It includes the following policy changes: * All GetRand* functions seed the stack pointer and rdrand result (in addition to the performance counter) * The periodic entropy added by the idle scheduler now seeds stack pointer, rdrand and perfmon data (once every 10 minutes) in addition to just a sleep timing. * The entropy added when calling GetStrongRandBytes no longer includes the once-per-10-minutes perfmon data on windows (it is moved to the idle scheduler instead, where latency matters less). Other changes: * OpenSSL is no longer seeded directly anywhere. Instead, any generated randomness through our own RNG is fed back to OpenSSL (after an additional hashing step to prevent leaking our RNG state). * Seeding that was previously done directly in RandAddSeedSleep is now moved to SeedSleep(), which is indirectly invoked through ProcRand from RandAddSeedSleep. * Seeding that was previously done directly in GetStrongRandBytes() is now moved to SeedSlow(), which is indirectly invoked through ProcRand from GetStrongRandBytes().
1 parent 16e40a8 commit 9d7032e

File tree

3 files changed

+138
-80
lines changed

3 files changed

+138
-80
lines changed

src/random.cpp

Lines changed: 118 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,8 @@ static bool GetHardwareRand(unsigned char* ent32) {
145145
return false;
146146
}
147147

148-
void RandAddSeed()
148+
static void RandAddSeedPerfmon(CSHA512& hasher)
149149
{
150-
// Seed with CPU performance counter
151-
int64_t nCounter = GetPerformanceCounter();
152-
RAND_add(&nCounter, sizeof(nCounter), 1.5);
153-
memory_cleanse((void*)&nCounter, sizeof(nCounter));
154-
}
155-
156-
static void RandAddSeedPerfmon()
157-
{
158-
RandAddSeed();
159-
160150
#ifdef WIN32
161151
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom
162152
// Seed with the entire set of perfmon data
@@ -180,7 +170,7 @@ static void RandAddSeedPerfmon()
180170
}
181171
RegCloseKey(HKEY_PERFORMANCE_DATA);
182172
if (ret == ERROR_SUCCESS) {
183-
RAND_add(vData.data(), nSize, nSize / 100.0);
173+
hasher.Write(vData.data(), nSize);
184174
memory_cleanse(vData.data(), nSize);
185175
} else {
186176
// Performance data is only a best-effort attempt at improving the
@@ -288,13 +278,6 @@ void GetOSRand(unsigned char *ent32)
288278
#endif
289279
}
290280

291-
void GetRandBytes(unsigned char* buf, int num)
292-
{
293-
if (RAND_bytes(buf, num) != 1) {
294-
RandFailure();
295-
}
296-
}
297-
298281
void LockingCallbackOpenSSL(int mode, int i, const char* file, int line);
299282

300283
namespace {
@@ -303,6 +286,7 @@ struct RNGState {
303286
Mutex m_mutex;
304287
unsigned char m_state[32] GUARDED_BY(m_mutex) = {0};
305288
uint64_t m_counter GUARDED_BY(m_mutex) = 0;
289+
bool m_strongly_seeded GUARDED_BY(m_mutex) = false;
306290
std::unique_ptr<Mutex[]> m_mutex_openssl;
307291

308292
RNGState()
@@ -319,14 +303,6 @@ struct RNGState {
319303
// or corrupt. Explicitly tell OpenSSL not to try to load the file. The result for our libs will be
320304
// that the config appears to have been loaded and there are no modules/engines available.
321305
OPENSSL_no_config();
322-
323-
#ifdef WIN32
324-
// Seed OpenSSL PRNG with current contents of the screen
325-
RAND_screen();
326-
#endif
327-
328-
// Seed OpenSSL PRNG with performance counter
329-
RandAddSeed();
330306
}
331307

332308
~RNGState()
@@ -337,14 +313,19 @@ struct RNGState {
337313
CRYPTO_set_locking_callback(nullptr);
338314
}
339315

340-
/** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. */
341-
void MixExtract(unsigned char* out, size_t num, CSHA512&& hasher)
316+
/** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher.
317+
*
318+
* If this function has never been called with strong_seed = true, false is returned.
319+
*/
320+
bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed)
342321
{
343322
assert(num <= 32);
344323
unsigned char buf[64];
345324
static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size");
325+
bool ret;
346326
{
347327
LOCK(m_mutex);
328+
ret = (m_strongly_seeded |= strong_seed);
348329
// Write the current state of the RNG into the hasher
349330
hasher.Write(m_state, 32);
350331
// Write a new counter number into the state
@@ -363,6 +344,7 @@ struct RNGState {
363344
// Best effort cleanup of internal state
364345
hasher.Reset();
365346
memory_cleanse(buf, 64);
347+
return ret;
366348
}
367349
};
368350

@@ -386,61 +368,128 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THRE
386368
}
387369
}
388370

389-
static void AddDataToRng(void* data, size_t len, RNGState& rng);
371+
static void SeedTimestamp(CSHA512& hasher)
372+
{
373+
int64_t perfcounter = GetPerformanceCounter();
374+
hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter));
375+
}
390376

391-
void RandAddSeedSleep()
377+
static void SeedFast(CSHA512& hasher)
392378
{
393-
RNGState& rng = GetRNGState();
379+
unsigned char buffer[32];
394380

395-
int64_t nPerfCounter1 = GetPerformanceCounter();
396-
std::this_thread::sleep_for(std::chrono::milliseconds(1));
397-
int64_t nPerfCounter2 = GetPerformanceCounter();
381+
// Stack pointer to indirectly commit to thread/callstack
382+
const unsigned char* ptr = buffer;
383+
hasher.Write((const unsigned char*)&ptr, sizeof(ptr));
398384

399-
// Combine with and update state
400-
AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1), rng);
401-
AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2), rng);
385+
// Hardware randomness is very fast when available; use it always.
386+
bool have_hw_rand = GetHardwareRand(buffer);
387+
if (have_hw_rand) hasher.Write(buffer, sizeof(buffer));
402388

403-
memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1));
404-
memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2));
389+
// High-precision timestamp
390+
SeedTimestamp(hasher);
405391
}
406392

407-
static void AddDataToRng(void* data, size_t len, RNGState& rng) {
408-
CSHA512 hasher;
409-
hasher.Write((const unsigned char*)&len, sizeof(len));
410-
hasher.Write((const unsigned char*)data, len);
411-
rng.MixExtract(nullptr, 0, std::move(hasher));
393+
static void SeedSlow(CSHA512& hasher)
394+
{
395+
unsigned char buffer[32];
396+
397+
// Everything that the 'fast' seeder includes
398+
SeedFast(hasher);
399+
400+
// OS randomness
401+
GetOSRand(buffer);
402+
hasher.Write(buffer, sizeof(buffer));
403+
404+
// OpenSSL RNG (for now)
405+
RAND_bytes(buffer, sizeof(buffer));
406+
hasher.Write(buffer, sizeof(buffer));
407+
408+
// High-precision timestamp.
409+
//
410+
// Note that we also commit to a timestamp in the Fast seeder, so we indirectly commit to a
411+
// benchmark of all the entropy gathering sources in this function).
412+
SeedTimestamp(hasher);
412413
}
413414

414-
void GetStrongRandBytes(unsigned char* out, int num)
415+
static void SeedSleep(CSHA512& hasher)
415416
{
416-
RNGState& rng = GetRNGState();
417+
// Everything that the 'fast' seeder includes
418+
SeedFast(hasher);
417419

418-
assert(num <= 32);
419-
CSHA512 hasher;
420-
unsigned char buf[64];
420+
// High-precision timestamp
421+
SeedTimestamp(hasher);
421422

422-
// First source: OpenSSL's RNG
423-
RandAddSeedPerfmon();
424-
GetRandBytes(buf, 32);
425-
hasher.Write(buf, 32);
423+
// Sleep for 1ms
424+
MilliSleep(1);
426425

427-
// Second source: OS RNG
428-
GetOSRand(buf);
429-
hasher.Write(buf, 32);
426+
// High-precision timestamp after sleeping (as we commit to both the time before and after, this measures the delay)
427+
SeedTimestamp(hasher);
430428

431-
// Third source: HW RNG, if available.
432-
if (GetHardwareRand(buf)) {
433-
hasher.Write(buf, 32);
429+
// Windows performance monitor data (once every 10 minutes)
430+
RandAddSeedPerfmon(hasher);
431+
}
432+
433+
static void SeedStartup(CSHA512& hasher)
434+
{
435+
#ifdef WIN32
436+
RAND_screen();
437+
#endif
438+
439+
// Everything that the 'slow' seeder includes.
440+
SeedSlow(hasher);
441+
442+
// Windows performance monitor data.
443+
RandAddSeedPerfmon(hasher);
444+
}
445+
446+
enum class RNGLevel {
447+
FAST, //!< Automatically called by GetRandBytes
448+
SLOW, //!< Automatically called by GetStrongRandBytes
449+
SLEEP, //!< Called by RandAddSeedSleep()
450+
};
451+
452+
static void ProcRand(unsigned char* out, int num, RNGLevel level)
453+
{
454+
// Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available).
455+
RNGState& rng = GetRNGState();
456+
457+
assert(num <= 32);
458+
459+
CSHA512 hasher;
460+
switch (level) {
461+
case RNGLevel::FAST:
462+
SeedFast(hasher);
463+
break;
464+
case RNGLevel::SLOW:
465+
SeedSlow(hasher);
466+
break;
467+
case RNGLevel::SLEEP:
468+
SeedSleep(hasher);
469+
break;
434470
}
435471

436472
// Combine with and update state
437-
rng.MixExtract(out, num, std::move(hasher));
473+
if (!rng.MixExtract(out, num, std::move(hasher), false)) {
474+
// On the first invocation, also seed with SeedStartup().
475+
CSHA512 startup_hasher;
476+
SeedStartup(startup_hasher);
477+
rng.MixExtract(out, num, std::move(startup_hasher), true);
478+
}
438479

439-
// Produce output
440-
memcpy(out, buf, num);
441-
memory_cleanse(buf, 64);
480+
// For anything but the 'fast' level, feed the resulting RNG output (after an additional hashing step) back into OpenSSL.
481+
if (level != RNGLevel::FAST) {
482+
unsigned char buf[64];
483+
CSHA512().Write(out, num).Finalize(buf);
484+
RAND_add(buf, sizeof(buf), num);
485+
memory_cleanse(buf, 64);
486+
}
442487
}
443488

489+
void GetRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::FAST); }
490+
void GetStrongRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::SLOW); }
491+
void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); }
492+
444493
uint64_t GetRand(uint64_t nMax)
445494
{
446495
if (nMax == 0)
@@ -539,8 +588,10 @@ bool Random_SanityCheck()
539588
if (stop == start) return false;
540589

541590
// We called GetPerformanceCounter. Use it as entropy.
542-
RAND_add((const unsigned char*)&start, sizeof(start), 1);
543-
RAND_add((const unsigned char*)&stop, sizeof(stop), 1);
591+
CSHA512 to_add;
592+
to_add.Write((const unsigned char*)&start, sizeof(start));
593+
to_add.Write((const unsigned char*)&stop, sizeof(stop));
594+
GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false);
544595

545596
return true;
546597
}
@@ -571,7 +622,7 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce
571622
void RandomInit()
572623
{
573624
// Invoke RNG code to trigger initialization (if not already performed)
574-
GetRNGState();
625+
ProcRand(nullptr, 0, RNGLevel::FAST);
575626

576627
ReportHardwareRand();
577628
}

src/random.h

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,40 @@
1313
#include <stdint.h>
1414
#include <limits>
1515

16-
/* Seed OpenSSL PRNG with additional entropy data */
17-
void RandAddSeed();
18-
1916
/**
20-
* Functions to gather random data via the OpenSSL PRNG
17+
* Generate random data via the internal PRNG.
18+
*
19+
* These functions are designed to be fast (sub microsecond), but do not necessarily
20+
* meaningfully add entropy to the PRNG state.
21+
*
22+
* Thread-safe.
2123
*/
2224
void GetRandBytes(unsigned char* buf, int num);
2325
uint64_t GetRand(uint64_t nMax);
2426
int GetRandInt(int nMax);
2527
uint256 GetRandHash();
2628

2729
/**
28-
* Add a little bit of randomness to the output of GetStrongRangBytes.
29-
* This sleeps for a millisecond, so should only be called when there is
30-
* no other work to be done.
30+
* Gather entropy from various sources, feed it into the internal PRNG, and
31+
* generate random data using it.
32+
*
33+
* This function will cause failure whenever the OS RNG fails.
34+
*
35+
* Thread-safe.
3136
*/
32-
void RandAddSeedSleep();
37+
void GetStrongRandBytes(unsigned char* buf, int num);
3338

3439
/**
35-
* Function to gather random data from multiple sources, failing whenever any
36-
* of those sources fail to provide a result.
40+
* Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state.
41+
*
42+
* Thread-safe.
3743
*/
38-
void GetStrongRandBytes(unsigned char* buf, int num);
44+
void RandAddSeedSleep();
3945

4046
/**
4147
* Fast randomness source. This is seeded once with secure random data, but
42-
* is completely deterministic and insecure after that.
48+
* is completely deterministic and does not gather more entropy after that.
49+
*
4350
* This class is not thread-safe.
4451
*/
4552
class FastRandomContext {

src/scheduler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void CScheduler::serviceQueue()
4141
try {
4242
if (!shouldStop() && taskQueue.empty()) {
4343
reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock);
44-
// Use this chance to get a tiny bit more entropy
44+
// Use this chance to get more entropy
4545
RandAddSeedSleep();
4646
}
4747
while (!shouldStop() && taskQueue.empty()) {

0 commit comments

Comments
 (0)