diff --git a/src/random.h b/src/random.h --- a/src/random.h +++ b/src/random.h @@ -14,12 +14,12 @@ #include /** - * Seed OpenSSL PRNG with additional entropy data. - */ -void RandAddSeed(); - -/** - * Functions to gather random data via the OpenSSL PRNG + * Generate random data via the internal PRNG. + * + * These functions are designed to be fast (sub microsecond), but do not + * necessarily meaningfully add entropy to the PRNG state. + * + * Thread-safe. */ void GetRandBytes(uint8_t *buf, int num); uint64_t GetRand(uint64_t nMax); @@ -27,21 +27,27 @@ uint256 GetRandHash(); /** - * Add a little bit of randomness to the output of GetStrongRangBytes. - * This sleeps for a millisecond, so should only be called when there is no - * other work to be done. + * Gather entropy from various sources, feed it into the internal PRNG, and + * generate random data using it. + * + * This function will cause failure whenever the OS RNG fails. + * + * Thread-safe. */ -void RandAddSeedSleep(); +void GetStrongRandBytes(uint8_t *buf, int num); /** - * Function to gather random data from multiple sources, failing whenever any of - * those sources fail to provide a result. + * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG + * state. + * + * Thread-safe. */ -void GetStrongRandBytes(uint8_t *buf, int num); +void RandAddSeedSleep(); /** - * Fast randomness source. This is seeded once with secure random data, but is - * completely deterministic and insecure after that. + * Fast randomness source. This is seeded once with secure random data, but + * is completely deterministic and does not gather more entropy after that. + * * This class is not thread-safe. */ class FastRandomContext { diff --git a/src/random.cpp b/src/random.cpp --- a/src/random.cpp +++ b/src/random.cpp @@ -151,16 +151,7 @@ return false; } -void RandAddSeed() { - // Seed with CPU performance counter - int64_t nCounter = GetPerformanceCounter(); - RAND_add(&nCounter, sizeof(nCounter), 1.5); - memory_cleanse((void *)&nCounter, sizeof(nCounter)); -} - -static void RandAddSeedPerfmon() { - RandAddSeed(); - +static void RandAddSeedPerfmon(CSHA512 &hasher) { #ifdef WIN32 // Don't need this on Linux, OpenSSL automatically uses /dev/urandom // Seed with the entire set of perfmon data @@ -189,7 +180,7 @@ } RegCloseKey(HKEY_PERFORMANCE_DATA); if (ret == ERROR_SUCCESS) { - RAND_add(vData.data(), nSize, nSize / 100.0); + hasher.Write(vData.data(), nSize); memory_cleanse(vData.data(), nSize); } else { // Performance data is only a best-effort attempt at improving the @@ -301,12 +292,6 @@ #endif } -void GetRandBytes(uint8_t *buf, int num) { - if (RAND_bytes(buf, num) != 1) { - RandFailure(); - } -} - void LockingCallbackOpenSSL(int mode, int i, const char *file, int line); namespace { @@ -315,6 +300,7 @@ Mutex m_mutex; uint8_t m_state[32] GUARDED_BY(m_mutex) = {0}; uint64_t m_counter GUARDED_BY(m_mutex) = 0; + bool m_strongly_seeded GUARDED_BY(m_mutex) = false; std::unique_ptr m_mutex_openssl; RNGState() { @@ -332,14 +318,6 @@ // the file. The result for our libs will be that the config appears to // have been loaded and there are no modules/engines available. OPENSSL_no_config(); - -#ifdef WIN32 - // Seed OpenSSL PRNG with current contents of the screen - RAND_screen(); -#endif - - // Seed OpenSSL PRNG with performance counter - RandAddSeed(); } ~RNGState() { @@ -352,14 +330,20 @@ /** * Extract up to 32 bytes of entropy from the RNG state, mixing in new * entropy from hasher. + * + * If this function has never been called with strong_seed = true, false is + * returned. */ - void MixExtract(uint8_t *out, size_t num, CSHA512 &&hasher) { + bool MixExtract(uint8_t *out, size_t num, CSHA512 &&hasher, + bool strong_seed) { assert(num <= 32); uint8_t buf[64]; static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size"); + bool ret; { LOCK(m_mutex); + ret = (m_strongly_seeded |= strong_seed); // Write the current state of the RNG into the hasher hasher.Write(m_state, 32); // Write a new counter number into the state @@ -379,6 +363,7 @@ // Best effort cleanup of internal state hasher.Reset(); memory_cleanse(buf, 64); + return ret; } }; @@ -402,57 +387,132 @@ } } -static void AddDataToRng(void *data, size_t len, RNGState &rng); +static void SeedTimestamp(CSHA512 &hasher) { + int64_t perfcounter = GetPerformanceCounter(); + hasher.Write((const uint8_t *)&perfcounter, sizeof(perfcounter)); +} -void RandAddSeedSleep() { - RNGState &rng = GetRNGState(); +static void SeedFast(CSHA512 &hasher) { + uint8_t buffer[32]; - int64_t nPerfCounter1 = GetPerformanceCounter(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - int64_t nPerfCounter2 = GetPerformanceCounter(); + // Stack pointer to indirectly commit to thread/callstack + const uint8_t *ptr = buffer; + hasher.Write((const uint8_t *)&ptr, sizeof(ptr)); - // Combine with and update state - AddDataToRng(&nPerfCounter1, sizeof(nPerfCounter1), rng); - AddDataToRng(&nPerfCounter2, sizeof(nPerfCounter2), rng); + // Hardware randomness is very fast when available; use it always. + bool have_hw_rand = GetHardwareRand(buffer); + if (have_hw_rand) { + hasher.Write(buffer, sizeof(buffer)); + } - memory_cleanse(&nPerfCounter1, sizeof(nPerfCounter1)); - memory_cleanse(&nPerfCounter2, sizeof(nPerfCounter2)); + // High-precision timestamp + SeedTimestamp(hasher); } -static void AddDataToRng(void *data, size_t len, RNGState &rng) { - CSHA512 hasher; - hasher.Write((const uint8_t *)&len, sizeof(len)); - hasher.Write((const uint8_t *)data, len); - rng.MixExtract(nullptr, 0, std::move(hasher)); +static void SeedSlow(CSHA512 &hasher) { + uint8_t buffer[32]; + + // Everything that the 'fast' seeder includes + SeedFast(hasher); + + // OS randomness + GetOSRand(buffer); + hasher.Write(buffer, sizeof(buffer)); + + // OpenSSL RNG (for now) + RAND_bytes(buffer, sizeof(buffer)); + hasher.Write(buffer, sizeof(buffer)); + + // High-precision timestamp. + // + // Note that we also commit to a timestamp in the Fast seeder, so we + // indirectly commit to a benchmark of all the entropy gathering sources in + // this function). + SeedTimestamp(hasher); } -void GetStrongRandBytes(uint8_t *out, int num) { - RNGState &rng = GetRNGState(); +static void SeedSleep(CSHA512 &hasher) { + // Everything that the 'fast' seeder includes + SeedFast(hasher); - assert(num <= 32); - CSHA512 hasher; - uint8_t buf[64]; + // High-precision timestamp + SeedTimestamp(hasher); + + // Sleep for 1ms + MilliSleep(1); - // First source: OpenSSL's RNG - RandAddSeedPerfmon(); - GetRandBytes(buf, 32); - hasher.Write(buf, 32); + // High-precision timestamp after sleeping (as we commit to both the time + // before and after, this measures the delay) + SeedTimestamp(hasher); - // Second source: OS RNG - GetOSRand(buf); - hasher.Write(buf, 32); + // Windows performance monitor data (once every 10 minutes) + RandAddSeedPerfmon(hasher); +} + +static void SeedStartup(CSHA512 &hasher) { +#ifdef WIN32 + RAND_screen(); +#endif + + // Everything that the 'slow' seeder includes. + SeedSlow(hasher); + + // Windows performance monitor data. + RandAddSeedPerfmon(hasher); +} - // Third source: HW RNG, if available. - if (GetHardwareRand(buf)) { - hasher.Write(buf, 32); +enum class RNGLevel { + FAST, //!< Automatically called by GetRandBytes + SLOW, //!< Automatically called by GetStrongRandBytes + SLEEP, //!< Called by RandAddSeedSleep() +}; + +static void ProcRand(uint8_t *out, int num, RNGLevel level) { + // Make sure the RNG is initialized first (as all Seed* function possibly + // need hwrand to be available). + RNGState &rng = GetRNGState(); + + assert(num <= 32); + + CSHA512 hasher; + switch (level) { + case RNGLevel::FAST: + SeedFast(hasher); + break; + case RNGLevel::SLOW: + SeedSlow(hasher); + break; + case RNGLevel::SLEEP: + SeedSleep(hasher); + break; } // Combine with and update state - rng.MixExtract(out, num, std::move(hasher)); + if (!rng.MixExtract(out, num, std::move(hasher), false)) { + // On the first invocation, also seed with SeedStartup(). + CSHA512 startup_hasher; + SeedStartup(startup_hasher); + rng.MixExtract(out, num, std::move(startup_hasher), true); + } - // Produce output - memcpy(out, buf, num); - memory_cleanse(buf, 64); + // For anything but the 'fast' level, feed the resulting RNG output (after + // an additional hashing step) back into OpenSSL. + if (level != RNGLevel::FAST) { + uint8_t buf[64]; + CSHA512().Write(out, num).Finalize(buf); + RAND_add(buf, sizeof(buf), num); + memory_cleanse(buf, 64); + } +} + +void GetRandBytes(uint8_t *buf, int num) { + ProcRand(buf, num, RNGLevel::FAST); +} +void GetStrongRandBytes(uint8_t *buf, int num) { + ProcRand(buf, num, RNGLevel::SLOW); +} +void RandAddSeedSleep() { + ProcRand(nullptr, 0, RNGLevel::SLEEP); } uint64_t GetRand(uint64_t nMax) { @@ -560,8 +620,10 @@ } // We called GetPerformanceCounter. Use it as entropy. - RAND_add((const uint8_t *)&start, sizeof(start), 1); - RAND_add((const uint8_t *)&stop, sizeof(stop), 1); + CSHA512 to_add; + to_add.Write((const uint8_t *)&start, sizeof(start)); + to_add.Write((const uint8_t *)&stop, sizeof(stop)); + GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false); return true; } @@ -592,7 +654,7 @@ void RandomInit() { // Invoke RNG code to trigger initialization (if not already performed) - GetRNGState(); + ProcRand(nullptr, 0, RNGLevel::FAST); ReportHardwareRand(); } diff --git a/src/scheduler.cpp b/src/scheduler.cpp --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -27,7 +27,7 @@ try { if (!shouldStop() && taskQueue.empty()) { reverse_lock> rlock(lock); - // Use this chance to get a tiny bit more entropy + // Use this chance to get more entropy RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) {