Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional support for external PRNGs: make source of entropy configurable #897

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/core/include/math/distributiongenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@
#include <string>

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.
Expand All @@ -69,7 +65,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<PRNG> m_prng;
Expand Down
2 changes: 1 addition & 1 deletion src/core/include/utils/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void MoveAppend(std::vector<X>& dst, std::vector<X>& src) {
}
}

void secure_memset(void* mem, uint8_t c, size_t len);
void secure_memset(volatile void* mem, uint8_t c, size_t len);

} // namespace lbcrypto

Expand Down
24 changes: 19 additions & 5 deletions src/core/include/utils/prng/blake2engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "utils/prng/prng.h"

#include <cstddef>
#include <array>

namespace default_prng {
/**
Expand All @@ -47,17 +48,24 @@ namespace default_prng {
*/
class Blake2Engine : public PRNG {
public:
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<PRNG::result_type, MAX_SEED_GENS>;

/**
* @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) {}
explicit Blake2Engine(const blake2_seed_array_t& seed, uint64_t counter) : m_seed(seed), m_counter(counter) {}

/**
* @brief main call to the PRNG
*/
PRNG::result_type operator()() override {
if (m_bufferIndex == static_cast<size_t>(PRNG::PRNG_BUFFER_SIZE))
if (m_bufferIndex == static_cast<size_t>(PRNG_BUFFER_SIZE))
m_bufferIndex = 0;

// makes a call to the BLAKE2 generator only when the currently buffered values are all consumed precomputations and
Expand All @@ -78,19 +86,25 @@ class Blake2Engine : public PRNG {
void Generate();

// The vector that stores random samples generated using the hash function
std::array<PRNG::result_type, PRNG::PRNG_BUFFER_SIZE> m_buffer{};
std::array<PRNG::result_type, PRNG_BUFFER_SIZE> 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
Expand Down
37 changes: 10 additions & 27 deletions src/core/include/utils/prng/prng.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -50,53 +50,36 @@

#include <cstdint>
#include <limits>
#include <array>


// 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<result_type, MAX_SEED_GENS>;
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<result_type>::min();
return std::numeric_limits<result_type>::min();
}

/**
* @brief maximum value used by C++11 distribution generators when no upper
* bound is explicitly specified by the user
*/
static constexpr result_type max() {
return std::numeric_limits<result_type>::max();
return std::numeric_limits<result_type>::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__
149 changes: 32 additions & 117 deletions src/core/lib/math/distributiongenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,156 +36,71 @@

#include "math/distributiongenerator.h"
#include "utils/prng/blake2engine.h"
#include "utils/memory.h"
#include "utils/exception.h"

#include <chrono>
#include <cstdint>
#include <random>
#include <thread>
#include <iostream>
#if (defined(__linux__) || defined(__unix__)) && !defined(__APPLE__) && defined(__GNUC__) && !defined(__clang__)
#include <dlfcn.h>
#endif

namespace lbcrypto {

std::shared_ptr<PRNG> PseudoRandomNumberGenerator::m_prng = nullptr;
std::shared_ptr<PRNG> 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::thread::id>{}(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::thread::id>{}(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<uint64_t>(mem);
free(mem);

std::uniform_int_distribution<uint32_t> 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<PRNG> 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<PRNG>(genPRNGEngine(seed, 0));
// re-init seed for security reasons
secure_memset(seed.data(), 0, bytes_to_clear);
m_prng = std::shared_ptr<PRNG>(genPRNGEngine());
if (!m_prng)
OPENFHE_THROW("Cannot create a PRNG engine");
} // pragma omp critical
} // pragma omp critical
}
return *m_prng;
}
Expand Down
8 changes: 4 additions & 4 deletions src/core/lib/utils/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading