diff --git a/src/core/include/math/distributiongenerator.h b/src/core/include/math/distributiongenerator.h index 70cbbebe..0990b0cf 100644 --- a/src/core/include/math/distributiongenerator.h +++ b/src/core/include/math/distributiongenerator.h @@ -43,10 +43,6 @@ #include namespace lbcrypto { -// if FIXED_SEED is defined, then PRNG uses a fixed seed number for reproducible results during debug. -// Use only one OMP thread to ensure reproducibility -// #define FIXED_SEED - /** * @brief PseudoRandomNumberGenerator provides the PRNG capability to all random distribution generators in OpenFHE. @@ -59,7 +55,8 @@ class PseudoRandomNumberGenerator { * @brief InitPRNGEngine() initializes the PRNG generator * @param libPath a string with the absolute path to an external PRNG library ("/path/to/libprng.so"). * If the string is empty, then the default (OpenFHE's built-in PRNG) library will be used. - * @note this function should be called at the beginning of main() if an external library to be used + * @note this function should be called at the beginning of main() if an external library to be used and + * prints a trace in this case. There is no trace for the built-in PRNG */ static void InitPRNGEngine(const std::string& libPath = std::string()); @@ -69,7 +66,7 @@ class PseudoRandomNumberGenerator { static PRNG& GetPRNG(); private: - using GenPRNGEngineFuncPtr = PRNG* (*)(const PRNG::seed_array_t&, uint64_t counter); + using GenPRNGEngineFuncPtr = PRNG* (*)(); // shared pointer to a thread-specific PRNG engine static std::shared_ptr m_prng; diff --git a/src/core/include/utils/memory.h b/src/core/include/utils/memory.h index 99ebf962..d8c08164 100644 --- a/src/core/include/utils/memory.h +++ b/src/core/include/utils/memory.h @@ -80,7 +80,12 @@ void MoveAppend(std::vector& dst, std::vector& src) { } } -void secure_memset(void* mem, uint8_t c, size_t len); +/** + * @brief secure_memset() is a function with the same functionality which is provided by std::memset. + * Usually, the compiler optimizes a call to std::memset out if it is called for a memory which goes out of scope. + * This function is never optimized out and used to re-initialize a memory for security reasons. + */ +void secure_memset(volatile void* mem, uint8_t c, size_t len); } // namespace lbcrypto diff --git a/src/core/include/utils/prng/blake2engine.h b/src/core/include/utils/prng/blake2engine.h index 4ac7fa48..25dcf8a7 100644 --- a/src/core/include/utils/prng/blake2engine.h +++ b/src/core/include/utils/prng/blake2engine.h @@ -39,6 +39,7 @@ #include "utils/prng/prng.h" #include +#include namespace default_prng { /** @@ -47,29 +48,38 @@ namespace default_prng { */ class Blake2Engine : public PRNG { public: - /** - * @brief Main constructor taking a vector of MAX_SEED_GENS integers as a seed and a counter. - * If there is no value for the counter, then pass zero as the counter value - */ - explicit Blake2Engine(const PRNG::seed_array_t& seed, uint64_t counter) : PRNG(seed, counter) {} - - /** - * @brief main call to the PRNG - */ - PRNG::result_type operator()() override { - if (m_bufferIndex == static_cast(PRNG::PRNG_BUFFER_SIZE)) - m_bufferIndex = 0; - - // makes a call to the BLAKE2 generator only when the currently buffered values are all consumed precomputations and - // done only once for the current buffer - if (m_bufferIndex == 0) - Generate(); - - PRNG::result_type result = m_buffer[m_bufferIndex]; - m_bufferIndex++; - - return result; - } + enum { + MAX_SEED_GENS = 16, + // the buffer stores 1024 samples of 32-bit integers + PRNG_BUFFER_SIZE = 1024 + }; + using blake2_seed_array_t = std::array; + + /** + * @brief Main constructor taking an array of integers as a seed and a counter. + * If there is no value for the counter, then pass zero as the counter value + */ + explicit Blake2Engine(const blake2_seed_array_t& seed, uint64_t counter) : m_seed(seed), m_counter(counter) {} + + ~Blake2Engine(); + + /** + * @brief main call to the PRNG + */ + PRNG::result_type operator()() override { + if (m_bufferIndex == static_cast(PRNG_BUFFER_SIZE)) + m_bufferIndex = 0; + + // makes a call to the BLAKE2 generator only when the currently buffered values are all consumed precomputations and + // done only once for the current buffer + if (m_bufferIndex == 0) + Generate(); + + PRNG::result_type result = m_buffer[m_bufferIndex]; + m_bufferIndex++; + + return result; + } private: /** @@ -77,20 +87,26 @@ class Blake2Engine : public PRNG { */ void Generate(); - // The vector that stores random samples generated using the hash function - std::array m_buffer{}; + // The vector to store random samples generated using the hash function + std::array m_buffer{}; // Index in m_buffer corresponding to the current PRNG sample size_t m_bufferIndex = 0; + + // the seed for the hash function + blake2_seed_array_t m_seed{}; + + // counter used as input to the hash function; gets incremented after each call + uint64_t m_counter = 0; }; /** * @brief createEngineInstance() generates a Blake2Engine object which is dynamically allocated * @return pointer to the generated Blake2Engine object - * @attention the caller is responsible for freeing the memory allocated by this function + * @attention the caller is responsible for freeing the memory allocated by this function **/ extern "C" { - PRNG* createEngineInstance(const PRNG::seed_array_t& seed, uint64_t counter); + PRNG* createEngineInstance(); } } // namespace default_prng diff --git a/src/core/include/utils/prng/prng.h b/src/core/include/utils/prng/prng.h index c6483013..8860e363 100644 --- a/src/core/include/utils/prng/prng.h +++ b/src/core/include/utils/prng/prng.h @@ -31,12 +31,12 @@ /** * DISCLAIMER: IMPORTANT NOTICE ABOUT FILE MODIFICATIONS - * - * This file is used in OpenFHE's built-in PRNG and ANY EXTERNAL PRNG. + * + * This file is used in OpenFHE's built-in PRNG and ANY EXTERNAL PRNG. * The file is critical to the functionality and the security of the library. - * + * * Modifications should only be performed by personnel who understand the potential impacts. - * + * * By proceeding with changes to this file, you acknowledge that you understand the risks involved and * accept full responsibility for any resulting issues. */ @@ -50,31 +50,22 @@ #include #include -#include - // ATTENTION (VERY IMPORTANT): // for any engine class derived from the PRNG class there must be a C function named "createEngineInstance" -// returning a dynamically allocated object of that derived class (see how it is done in blake2engine.h) +// returning a dynamically allocated object of that derived class (see how it is done in blake2engine.h) class PRNG { public: - enum { - MAX_SEED_GENS = 16, - // the buffer stores 1024 samples of 32-bit integers - PRNG_BUFFER_SIZE = 1024 - }; - // all C++11 distributions used in OpenFHE work with uint32_t by default. // a different data type can be specified if needed for a particular architecture - using result_type = uint32_t; - using seed_array_t = std::array; + using result_type = uint32_t; /** * @brief minimum value used by C++11 distribution generators when no lower * bound is explicitly specified by the user */ static constexpr result_type min() { - return std::numeric_limits::min(); + return std::numeric_limits::min(); } /** @@ -82,21 +73,13 @@ class PRNG { * bound is explicitly specified by the user */ static constexpr result_type max() { - return std::numeric_limits::max(); + return std::numeric_limits::max(); } virtual result_type operator()() = 0; - virtual ~PRNG() = default; + virtual ~PRNG() = default; protected: PRNG() = default; - PRNG(const seed_array_t &seed, uint64_t counter) : m_counter(counter), m_seed(seed) {} - - // counter used as input to the hash function; gets incremented after each call - uint64_t m_counter = 0; - - // the seed for the hash function - seed_array_t m_seed{}; }; -#endif // __PRNG_H__ - +#endif // __PRNG_H__ diff --git a/src/core/lib/math/distributiongenerator.cpp b/src/core/lib/math/distributiongenerator.cpp index 29461fed..570f6e89 100644 --- a/src/core/lib/math/distributiongenerator.cpp +++ b/src/core/lib/math/distributiongenerator.cpp @@ -36,13 +36,8 @@ #include "math/distributiongenerator.h" #include "utils/prng/blake2engine.h" -#include "utils/memory.h" #include "utils/exception.h" -#include -#include -#include -#include #include #if (defined(__linux__) || defined(__unix__)) && !defined(__APPLE__) && defined(__GNUC__) && !defined(__clang__) #include @@ -50,142 +45,62 @@ namespace lbcrypto { -std::shared_ptr PseudoRandomNumberGenerator::m_prng = nullptr; +std::shared_ptr PseudoRandomNumberGenerator::m_prng = nullptr; PseudoRandomNumberGenerator::GenPRNGEngineFuncPtr PseudoRandomNumberGenerator::genPRNGEngine = nullptr; void PseudoRandomNumberGenerator::InitPRNGEngine(const std::string& libPath) { - if(genPRNGEngine) // if genPRNGEngine has already been initialized - return; + if (genPRNGEngine) // if genPRNGEngine has already been initialized + return; - if(libPath.empty()) { - // use the default OpenFHE PRNG that comes with the library + if (libPath.empty()) { + // use the default OpenFHE PRNG that comes with the library genPRNGEngine = default_prng::createEngineInstance; if (!genPRNGEngine) OPENFHE_THROW("Cannot find symbol: default_prng::createEngineInstance"); // std::cerr << "InitPRNGEngine: using local PRNG" << std::endl; } else { - #if (defined(__linux__) || defined(__unix__)) && !defined(__APPLE__) && defined(__GNUC__) && !defined(__clang__) - // enable this code for g++ on Linux only - // do not close libraryHandle, your application will crash if you do - void* libraryHandle = dlopen(libPath.c_str(), RTLD_LAZY); - if (!libraryHandle) { - std::string errMsg{std::string("Cannot open ") + libPath + ": "}; - const char* dlsym_error = dlerror(); - errMsg += dlsym_error; - OPENFHE_THROW(errMsg); - } - genPRNGEngine = (GenPRNGEngineFuncPtr)dlsym(libraryHandle, "createEngineInstance"); - if (!genPRNGEngine) { - std::string errMsg{std::string("Cannot load symbol createEngineInstance() from ") + libPath}; - const char* dlsym_error = dlerror(); - errMsg += ": "; - errMsg += dlsym_error; - dlclose(libraryHandle); - OPENFHE_THROW(errMsg); - } - std::cerr << __FUNCTION__ << ": using external PRNG" << std::endl; - #else - OPENFHE_THROW("OpenFHE may use an external PRNG library linked with g++ on Linux only"); - #endif +#if (defined(__linux__) || defined(__unix__)) && !defined(__APPLE__) && defined(__GNUC__) && !defined(__clang__) + // enable this code for g++ on Linux only + // do not close libraryHandle, your application will crash if you do + void* libraryHandle = dlopen(libPath.c_str(), RTLD_LAZY); + if (!libraryHandle) { + std::string errMsg{std::string("Cannot open ") + libPath + ": "}; + const char* dlsym_error = dlerror(); + errMsg += dlsym_error; + OPENFHE_THROW(errMsg); + } + genPRNGEngine = (GenPRNGEngineFuncPtr)dlsym(libraryHandle, "createEngineInstance"); + if (!genPRNGEngine) { + std::string errMsg{std::string("Cannot load symbol createEngineInstance() from ") + libPath}; + const char* dlsym_error = dlerror(); + errMsg += ": "; + errMsg += dlsym_error; + dlclose(libraryHandle); + OPENFHE_THROW(errMsg); + } + std::cerr << __FUNCTION__ << ": using external PRNG" << std::endl; +#else + OPENFHE_THROW("OpenFHE may use an external PRNG library linked with g++ on Linux only"); +#endif } -} +} PRNG& PseudoRandomNumberGenerator::GetPRNG() { // initialization of PRNGs if (m_prng == nullptr) { #pragma omp critical { - // we would like to believe that the block of code below is a good defense line + // we would like to believe that the block of code below is a good defense line if (!genPRNGEngine) InitPRNGEngine(); if (!genPRNGEngine) OPENFHE_THROW("Failure to initialize the PRNG engine"); - PRNG::seed_array_t seed{}; - #if defined(FIXED_SEED) - // Only used for debugging in the single-threaded mode. - std::cerr << "**FOR DEBUGGING ONLY!!!! Using fixed initializer for PRNG. " - "Use a single thread only, e.g., OMP_NUM_THREADS=1!" - << std::endl; - - seed[0] = 1; - #else - // A 512-bit seed is generated for each thread (this roughly corresponds - // to 256 bits of security). The seed is the sum of a random sample - // generated using std::random_device (typically works correctly in - // Linux, MacOS X, and MinGW starting with GCC 9.2) and a PRNG sample - // seeded from current time stamp, a hash of the current thread, and a - // memory location of a heap variable. The PRNG sample is added in - // case random_device is deterministic (happens on MinGW with GCC - // below 9.2). All future calls to PRNG use the seed generated here. - - // The code below derives randomness from time, thread id, and a memory - // location of a heap variable. This seed is relevant only if the - // implementation of random_device is deterministic (as in older - // versions of GCC in MinGW) - PRNG::seed_array_t initKey{}; - // high-resolution clock typically has a nanosecond tick period - // Arguably this may give up to 32 bits of entropy as the clock gets - // recycled every 4.3 seconds - initKey[0] = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - // A thread id is often close to being random (on most systems) - initKey[1] = std::hash{}(std::this_thread::get_id()); - // On a 64-bit machine, the thread id is 64 bits long - // skip on 32-bit arm architectures - #if !defined(__arm__) && !defined(__EMSCRIPTEN__) - if (sizeof(size_t) == 8) - initKey[2] = (std::hash{}(std::this_thread::get_id()) >> 32); - #endif - // heap variable; we are going to use the least 32 bits of its memory - // location as the counter. This will increase the entropy of the PRNG sample - void* mem = malloc(1); - uint64_t counter = reinterpret_cast(mem); - free(mem); - - std::uniform_int_distribution distribution(0); - // the code below is wrapped in to {} as we want to get rid of gen immediately after the loop - { - // "PRNG* gen" points at a dynamically allocated (using c++'s new()) memory!!! - std::unique_ptr gen(genPRNGEngine(initKey, counter)); - for (auto& s : seed) - s = distribution(*gen); - } - - PRNG::seed_array_t rdseed{}; - size_t attempts = 3; - bool rdGenPassed = false; - for (size_t i = 0; i < attempts && !rdGenPassed; ++i) { - try { - std::random_device genR; - for (auto& rds : rdseed) { - // we use the fact that there is no overflow for unsigned integers - // (from C++ standard) i.e., arithmetic mod 2^32 is performed. For - // the seed to be random, it is sufficient for one of the two - // samples below to be random. In almost all practical cases, - // distribution(genR) is random. We add distribution(gen) just in - // case there is an implementation issue with random_device (as in - // older MinGW systems). - rds = distribution(genR); - } - rdGenPassed = true; - } - catch (std::exception& e) { - } - } - for (uint32_t i = 0; i < PRNG::MAX_SEED_GENS; ++i) - seed[i] += rdseed[i]; - - // re-init rdseed for security reasons - const size_t bytes_to_clear = (rdseed.size()*sizeof(rdseed[0])); - secure_memset(rdseed.data(), 0, bytes_to_clear); - #endif // FIXED_SEED - m_prng = std::shared_ptr(genPRNGEngine(seed, 0)); - // re-init seed for security reasons - secure_memset(seed.data(), 0, bytes_to_clear); + m_prng = std::shared_ptr(genPRNGEngine()); if (!m_prng) OPENFHE_THROW("Cannot create a PRNG engine"); - } // pragma omp critical + } // pragma omp critical } return *m_prng; } diff --git a/src/core/lib/utils/memory.cpp b/src/core/lib/utils/memory.cpp index 9c4b6b44..89b0083f 100644 --- a/src/core/lib/utils/memory.cpp +++ b/src/core/lib/utils/memory.cpp @@ -32,10 +32,10 @@ namespace lbcrypto { -void secure_memset(void* mem, uint8_t c, size_t len) { - volatile uint8_t* volatile ptr = (volatile uint8_t* volatile)mem; - for(size_t i = 0; i< len; ++i) - *(ptr+i) = c; +void secure_memset(volatile void* mem, uint8_t c, size_t len) { + volatile uint8_t* ptr = (volatile uint8_t*)mem; + for (size_t i = 0; i < len; ++i) + *(ptr + i) = c; } } // namespace lbcrypto diff --git a/src/core/lib/utils/prng/blake2engine.cpp b/src/core/lib/utils/prng/blake2engine.cpp index 745be30e..8b24f611 100644 --- a/src/core/lib/utils/prng/blake2engine.cpp +++ b/src/core/lib/utils/prng/blake2engine.cpp @@ -31,9 +31,20 @@ #include "utils/prng/blake2engine.h" #include "utils/prng/blake2.h" #include "utils/exception.h" +#include "utils/memory.h" + +#include +#include +#include namespace default_prng { +Blake2Engine::~Blake2Engine() { + // IMPORTANT: re-init seed for security reasons + const size_t bytes_to_clear = (m_seed.size() * sizeof(m_seed[0])); + lbcrypto::secure_memset(m_seed.data(), 0, bytes_to_clear); +} + void Blake2Engine::Generate() { // m_counter is the input to the hash function // m_buffer is the output @@ -44,8 +55,105 @@ void Blake2Engine::Generate() { m_counter++; } -PRNG* createEngineInstance(const PRNG::seed_array_t& seed, uint64_t counter) { - return new Blake2Engine(seed, counter); +extern "C" { +// if FIXED_SEED is defined, then PRNG uses a fixed seed number for reproducible results during debug. +// Use only one OMP thread to ensure reproducibility +// #define FIXED_SEED + +/** + * @brief Blake2SeedGenerator generates the seed array for the Blake2 PRNG engine used by OpenFHE. + * This function should be declared as static to make it local to this source file. + */ +static void Blake2SeedGenerator(Blake2Engine::blake2_seed_array_t& seed) { +#if defined(FIXED_SEED) + // Only used for debugging in the single-threaded mode. + std::cerr << "**FOR DEBUGGING ONLY!!!! Using fixed initializer for PRNG. " + "Use a single thread only, e.g., OMP_NUM_THREADS=1!" + << std::endl; + + seed[0] = 1; +#else + // A 512-bit seed is generated for each thread (this roughly corresponds + // to 256 bits of security). The seed is the sum of a random sample + // generated using std::random_device (typically works correctly in + // Linux, MacOS X, and MinGW starting with GCC 9.2) and a PRNG sample + // seeded from current time stamp, a hash of the current thread, and a + // memory location of a heap variable. The PRNG sample is added in + // case random_device is deterministic (happens on MinGW with GCC + // below 9.2). All future calls to PRNG use the seed generated here. + + // The code below derives randomness from time, thread id, and a memory + // location of a heap variable. This seed is relevant only if the + // implementation of random_device is deterministic (as in older + // versions of GCC in MinGW) + Blake2Engine::blake2_seed_array_t initKey{}; + // high-resolution clock typically has a nanosecond tick period + // Arguably this may give up to 32 bits of entropy as the clock gets + // recycled every 4.3 seconds + initKey[0] = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + // A thread id is often close to being random (on most systems) + initKey[1] = std::hash{}(std::this_thread::get_id()); + // On a 64-bit machine, the thread id is 64 bits long + // skip on 32-bit arm architectures + #if !defined(__arm__) && !defined(__EMSCRIPTEN__) + if (sizeof(size_t) == 8) + initKey[2] = (std::hash{}(std::this_thread::get_id()) >> 32); + #endif + // heap variable; we are going to use up to 64 bits of its memory location as the counter. + // This will increase the entropy of the PRNG sample + void* mem = malloc(1); + uint64_t counter = reinterpret_cast(mem); + free(mem); + + std::uniform_int_distribution distribution(0); + Blake2Engine gen(initKey, counter); + for (auto& s : seed) + s = distribution(gen); + + Blake2Engine::blake2_seed_array_t rdseed{}; + size_t attempts = 3; + bool rdGenPassed = false; + for (size_t i = 0; i < attempts && !rdGenPassed; ++i) { + try { + std::random_device genR; + for (auto& rds : rdseed) { + // we use the fact that there is no overflow for unsigned integers + // (from C++ standard) i.e., arithmetic mod 2^32 is performed. For + // the seed to be random, it is sufficient for one of the two + // samples below to be random. In almost all practical cases, + // distribution(genR) is random. We add distribution(gen) just in + // case there is an implementation issue with random_device (as in + // older MinGW systems). + rds = distribution(genR); + } + rdGenPassed = true; + } + catch (std::exception& e) { + } + } + if (!rdGenPassed) + OPENFHE_THROW("std::random_device failed"); + + for (uint32_t i = 0; i < Blake2Engine::MAX_SEED_GENS; ++i) + seed[i] += rdseed[i]; + + // IMPORTANT: re-init rdseed for security reasons + const size_t bytes_to_clear = (rdseed.size() * sizeof(rdseed[0])); + lbcrypto::secure_memset(rdseed.data(), 0, bytes_to_clear); +#endif // FIXED_SEED +} +} + +PRNG* createEngineInstance() { + Blake2Engine::blake2_seed_array_t seed{}; + Blake2SeedGenerator(seed); + PRNG* ptr = new Blake2Engine(seed, 0); + + // IMPORTANT: re-init seed for security reasons + const size_t bytes_to_clear = (seed.size() * sizeof(seed[0])); + lbcrypto::secure_memset(seed.data(), 0, bytes_to_clear); + + return ptr; } } // namespace default_prng