diff options
Diffstat (limited to 'src/random.cpp')
-rw-r--r-- | src/random.cpp | 185 |
1 files changed, 118 insertions, 67 deletions
diff --git a/src/random.cpp b/src/random.cpp index 6699318ee0..ca54cc9962 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -145,18 +145,8 @@ static bool GetHardwareRand(unsigned char* ent32) { return false; } -void RandAddSeed() +static void RandAddSeedPerfmon(CSHA512& hasher) { - // 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(); - #ifdef WIN32 // Don't need this on Linux, OpenSSL automatically uses /dev/urandom // Seed with the entire set of perfmon data @@ -180,7 +170,7 @@ static void RandAddSeedPerfmon() } 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 @@ -288,13 +278,6 @@ void GetOSRand(unsigned char *ent32) #endif } -void GetRandBytes(unsigned char* buf, int num) -{ - if (RAND_bytes(buf, num) != 1) { - RandFailure(); - } -} - void LockingCallbackOpenSSL(int mode, int i, const char* file, int line); namespace { @@ -303,6 +286,7 @@ struct RNGState { Mutex m_mutex; unsigned char 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<Mutex[]> m_mutex_openssl; RNGState() @@ -319,14 +303,6 @@ struct RNGState { // or corrupt. Explicitly tell OpenSSL not to try to load 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() @@ -337,14 +313,19 @@ struct RNGState { CRYPTO_set_locking_callback(nullptr); } - /** Extract up to 32 bytes of entropy from the RNG state, mixing in new entropy from hasher. */ - void MixExtract(unsigned char* out, size_t num, CSHA512&& hasher) + /** 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. + */ + bool MixExtract(unsigned char* out, size_t num, CSHA512&& hasher, bool strong_seed) { assert(num <= 32); unsigned char 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 @@ -363,6 +344,7 @@ struct RNGState { // Best effort cleanup of internal state hasher.Reset(); memory_cleanse(buf, 64); + return ret; } }; @@ -386,61 +368,128 @@ void LockingCallbackOpenSSL(int mode, int i, const char* file, int line) NO_THRE } } -static void AddDataToRng(void* data, size_t len, RNGState& rng); +static void SeedTimestamp(CSHA512& hasher) +{ + int64_t perfcounter = GetPerformanceCounter(); + hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter)); +} -void RandAddSeedSleep() +static void SeedFast(CSHA512& hasher) { - RNGState& rng = GetRNGState(); + unsigned char 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 unsigned char* ptr = buffer; + hasher.Write((const unsigned char*)&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 unsigned char*)&len, sizeof(len)); - hasher.Write((const unsigned char*)data, len); - rng.MixExtract(nullptr, 0, std::move(hasher)); +static void SeedSlow(CSHA512& hasher) +{ + unsigned char 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(unsigned char* out, int num) +static void SeedSleep(CSHA512& hasher) { - RNGState& rng = GetRNGState(); + // Everything that the 'fast' seeder includes + SeedFast(hasher); - assert(num <= 32); - CSHA512 hasher; - unsigned char buf[64]; + // High-precision timestamp + SeedTimestamp(hasher); - // First source: OpenSSL's RNG - RandAddSeedPerfmon(); - GetRandBytes(buf, 32); - hasher.Write(buf, 32); + // Sleep for 1ms + MilliSleep(1); - // Second source: OS RNG - GetOSRand(buf); - 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); - // Third source: HW RNG, if available. - if (GetHardwareRand(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); +} + +enum class RNGLevel { + FAST, //!< Automatically called by GetRandBytes + SLOW, //!< Automatically called by GetStrongRandBytes + SLEEP, //!< Called by RandAddSeedSleep() +}; + +static void ProcRand(unsigned char* 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) { + unsigned char buf[64]; + CSHA512().Write(out, num).Finalize(buf); + RAND_add(buf, sizeof(buf), num); + memory_cleanse(buf, 64); + } } +void GetRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::FAST); } +void GetStrongRandBytes(unsigned char* buf, int num) { ProcRand(buf, num, RNGLevel::SLOW); } +void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } + uint64_t GetRand(uint64_t nMax) { if (nMax == 0) @@ -539,8 +588,10 @@ bool Random_SanityCheck() if (stop == start) return false; // We called GetPerformanceCounter. Use it as entropy. - RAND_add((const unsigned char*)&start, sizeof(start), 1); - RAND_add((const unsigned char*)&stop, sizeof(stop), 1); + CSHA512 to_add; + to_add.Write((const unsigned char*)&start, sizeof(start)); + to_add.Write((const unsigned char*)&stop, sizeof(stop)); + GetRNGState().MixExtract(nullptr, 0, std::move(to_add), false); return true; } @@ -571,7 +622,7 @@ FastRandomContext& FastRandomContext::operator=(FastRandomContext&& from) noexce void RandomInit() { // Invoke RNG code to trigger initialization (if not already performed) - GetRNGState(); + ProcRand(nullptr, 0, RNGLevel::FAST); ReportHardwareRand(); } |