C++ 如何简洁、便携、彻底地为mt19937 PRNG植入种子?

C++ 如何简洁、便携、彻底地为mt19937 PRNG植入种子?,c++,c++11,random,C++,C++11,Random,我似乎看到了许多答案,其中有人建议使用生成随机数,通常与以下代码一起使用: std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 5); dis(gen); 我们可能会用旧的方式来论证time(NULL)提供了低熵,time(NULL)是可预测的,并且最终结果是不一致的 但所有这一切都适用于新的方式:它只是有一个更闪亮的饰面 rd()返回单个无符号int。它至

我似乎看到了许多答案,其中有人建议使用
生成随机数,通常与以下代码一起使用:

std::random_device rd;  
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
我们可能会用旧的方式来论证
time(NULL)
提供了低熵,
time(NULL)
是可预测的,并且最终结果是不一致的

但所有这一切都适用于新的方式:它只是有一个更闪亮的饰面

  • rd()
    返回单个
    无符号int
    。它至少有16位,可能有32位。这还不足以播种MT的19937位状态

  • 使用
    std::mt19937 gen(rd());gen()
    (使用32位种子设定并查看第一个输出)无法提供良好的输出分布。7和13永远不能是第一个输出。两颗种子产生0。12粒种子产生1226181350粒种子。()

  • std::random_设备可以,有时也可以作为一个带有固定种子的简单PRNG来实现。因此,它可能会在每次运行时产生相同的序列。()这比
    时间(NULL)
    更糟糕

更糟糕的是,复制和粘贴前面的代码片段非常容易,尽管它们包含问题。解决这一问题的一些解决方案需要获得可能并不适合所有人的信息

鉴于此,我的问题是:“强”如何在C++中简洁、易懂、彻底地完成MT1937 PRNG的种子?>/P> 鉴于上述问题,一个很好的答案是:

  • 必须为mt19937/mt19937\u 64完全播种
  • 不能仅依靠
    std::random_设备
    时间(NULL)
    作为熵源
  • 不应依赖Boost或其他库
  • 应该适合在一个小的行数,这样它会看起来很好的副本粘贴到一个答案
思想

  • 我目前的想法是,
    std::random_device
    的输出可以与
    time(NULL)
    、派生值和硬编码常量(可以在分发过程中设置)混合在一起(可能通过异或),以尽最大努力获得熵

  • std::random\u device::entropy()
    很好地说明了std::random\u device
    可以做什么,也可以不做什么


从某种意义上讲,这是无法通过便携方式实现的。也就是说,我们可以构想一个运行C++的有效的完全确定的平台(例如,一个模拟地确定机器时钟的模拟器,并带有“确定的”I/O)。在这种情况下,没有随机性源来为PRNG种子。

我正在研究的实现利用了
mt19937的
state\u size
属性来决定初始化时提供多少种子:

using Generator = std::mt19937;

inline
auto const& random_data()
{
    thread_local static std::array<typename Generator::result_type, Generator::state_size> data;
    thread_local static std::random_device rd;

    std::generate(std::begin(data), std::end(data), std::ref(rd));

    return data;
}

inline
Generator& random_generator()
{
    auto const& data = random_data();

    thread_local static std::seed_seq seeds(std::begin(data), std::end(data));
    thread_local static Generator gen{seeds};

    return gen;
}

template<typename Number>
Number random_number(Number from, Number to)
{
    using Distribution = typename std::conditional
    <
        std::is_integral<Number>::value,
        std::uniform_int_distribution<Number>,
        std::uniform_real_distribution<Number>
    >::type;

    thread_local static Distribution dist;

    return dist(random_generator(), typename Distribution::param_type{from, to});
}
使用生成器=std::mt19937;
内联
自动常量和随机_数据()
{
线程\本地静态std::数组数据;
线程\本地静态标准::随机\设备rd;
std::generate(std::begin(数据)、std::end(数据)、std::ref(rd));
返回数据;
}
内联
生成器和随机_生成器()
{
自动常量和数据=随机_数据();
线程\本地静态std::seed \ seq seed(std::begin(数据)、std::end(数据));
线程_本地静态生成器gen{seeds};
返回发电机;
}
模板
数字随机数(数字从,数字到)
{
使用Distribution=typename std::conditional
<
std::is_integral::value,
标准:均匀分布,
标准:均匀实分布
>::类型;
螺纹_局部静态分布区;
return dist(random_generator(),typename分布::param_type{from,to});
}
我认为还有改进的余地,因为
std::random_device::result_type
在大小和范围上可能与
std::mt19937::result_type
有所不同,因此应该真正加以考虑

关于的注释。

根据
C++11(/14/17)
标准:

26.5.6随机类设备[rand.device]

2如果实现限制阻止生成非确定性随机数,则实现可采用随机数引擎

这意味着,如果由于某些限制而无法生成非确定性值,则实现只能生成确定性值


众所周知,Windows上的
MinGW
编译器不从其
std::random_设备
提供非确定性值,尽管这些值很容易从操作系统获得。因此,我认为这是一个bug,不太可能是跨实现和平台的常见现象。 我认为
std::random_设备的最大缺陷是,如果没有可用的CSPRNG,则允许确定性回退。这本身就是不使用
std::random_device
为PRNG种子的一个很好的理由,因为产生的字节可能是确定性的。不幸的是,它没有提供一个API来发现何时发生这种情况,或者请求失败,而不是提供低质量的随机数

也就是说,没有完全可移植的解决方案:但是,有一种体面的、最低限度的方法。您可以在CSPRNG(以下定义为
sysrandom
)周围使用最小包装器来为PRNG种子

窗户
您可以依赖CSPRNG的
CryptGenRandom
。例如,您可以使用以下代码:

bool acquire_context(HCRYPTPROV *ctx)
{
    if (!CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, 0)) {
        return CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET);
    }
    return true;
}


size_t sysrandom(void* dst, size_t dstlen)
{
    HCRYPTPROV ctx;
    if (!acquire_context(&ctx)) {
        throw std::runtime_error("Unable to initialize Win32 crypt library.");
    }

    BYTE* buffer = reinterpret_cast<BYTE*>(dst);
    if(!CryptGenRandom(ctx, dstlen, buffer)) {
        throw std::runtime_error("Unable to generate random bytes.");
    }

    if (!CryptReleaseContext(ctx, 0)) {
        throw std::runtime_error("Unable to release Win32 crypt library.");
    }

    return dstlen;
}
与Boost的比较
我们可以在快速查看以下内容后看到boost::random_设备(真正的CSPRNG)的相似之处。Boost在Windows上使用
MS_DEF_PROV
,这是
PROV_RSA_FULL
的提供程序类型。唯一缺少的是验证加密上下文,这可以通过
CRYPT\u VERIFYCONTEXT
完成。在*Nix上,Boost使用
/dev/uradom
。也就是说,这个解决方案是可移植的、经过良好测试的、易于使用的

Linux专门化
如果你愿意为了安全而牺牲简洁,那么e
bool acquire_context(HCRYPTPROV *ctx)
{
    if (!CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, 0)) {
        return CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET);
    }
    return true;
}


size_t sysrandom(void* dst, size_t dstlen)
{
    HCRYPTPROV ctx;
    if (!acquire_context(&ctx)) {
        throw std::runtime_error("Unable to initialize Win32 crypt library.");
    }

    BYTE* buffer = reinterpret_cast<BYTE*>(dst);
    if(!CryptGenRandom(ctx, dstlen, buffer)) {
        throw std::runtime_error("Unable to generate random bytes.");
    }

    if (!CryptReleaseContext(ctx, 0)) {
        throw std::runtime_error("Unable to release Win32 crypt library.");
    }

    return dstlen;
}
size_t sysrandom(void* dst, size_t dstlen)
{
    char* buffer = reinterpret_cast<char*>(dst);
    std::ifstream stream("/dev/urandom", std::ios_base::binary | std::ios_base::in);
    stream.read(buffer, dstlen);

    return dstlen;
}
std::uint_least32_t seed;    
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);
#if defined(__linux__) || defined(linux) || defined(__linux)
#   // Check the kernel version. `getrandom` is only Linux 3.17 and above.
#   include <linux/version.h>
#   if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
#       define HAVE_GETRANDOM
#   endif
#endif

// also requires glibc 2.25 for the libc wrapper
#if defined(HAVE_GETRANDOM)
#   include <sys/syscall.h>
#   include <linux/random.h>

size_t sysrandom(void* dst, size_t dstlen)
{
    int bytes = syscall(SYS_getrandom, dst, dstlen, 0);
    if (bytes != dstlen) {
        throw std::runtime_error("Unable to read N bytes from CSPRNG.");
    }

    return dstlen;
}

#elif defined(_WIN32)

// Windows sysrandom here.

#else

// POSIX sysrandom here.

#endif
#if defined(__OpenBSD__)
#   define HAVE_GETENTROPY
#endif

#if defined(HAVE_GETENTROPY)
#   include <unistd.h>

size_t sysrandom(void* dst, size_t dstlen)
{
    int bytes = getentropy(dst, dstlen);
    if (bytes != dstlen) {
        throw std::runtime_error("Unable to read N bytes from CSPRNG.");
    }

    return dstlen;
}

#endif
size_t sysrandom(void* dst, size_t dstlen)
{
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd == -1) {
        throw std::runtime_error("Unable to open /dev/urandom.");
    }
    if (read(fd, dst, dstlen) != dstlen) {
        close(fd);
        throw std::runtime_error("Unable to read N bytes from CSPRNG.");
    }

    close(fd);
    return dstlen;
}
#include <random>
#include <chrono>
#include <cstdint>
#include <algorithm>
#include <functional>
#include <iostream>

uint32_t LilEntropy(){
  //Gather many potential forms of entropy and XOR them
  const  uint32_t my_seed = 1273498732; //Change during distribution
  static uint32_t i = 0;        
  static std::random_device rd; 
  const auto hrclock = std::chrono::high_resolution_clock::now().time_since_epoch().count();
  const auto sclock  = std::chrono::system_clock::now().time_since_epoch().count();
  auto *heap         = malloc(1);
  const auto mash = my_seed + rd() + hrclock + sclock + (i++) +
    reinterpret_cast<intptr_t>(heap)    + reinterpret_cast<intptr_t>(&hrclock) +
    reinterpret_cast<intptr_t>(&i)      + reinterpret_cast<intptr_t>(&malloc)  +
    reinterpret_cast<intptr_t>(&LilEntropy);
  free(heap);
  return mash;
}

//Fully seed the mt19937 engine using as much entropy as we can get our
//hands on
void SeedGenerator(std::mt19937 &mt){
  std::uint_least32_t seed_data[std::mt19937::state_size];
  std::generate_n(seed_data, std::mt19937::state_size, std::ref(LilEntropy));
  std::seed_seq q(std::begin(seed_data), std::end(seed_data));
  mt.seed(q);
}

int main(){
  std::mt19937 mt;
  SeedGenerator(mt);

  for(int i=0;i<100;i++)
    std::cout<<mt()<<std::endl;
}
size_t sysrandom(void* dst, size_t dstlen); //from Alexander Huszagh answer above

void foo(){

    std::array<std::mt19937::UIntType, std::mt19937::state_size> state;
    sysrandom(state.begin(), state.length*sizeof(std::mt19937::UIntType));
    std::seed_seq s(state.begin(), state.end());

    std::mt19937 g;
    g.seed(s);
}
#include <cstdint> //`uint32_t`
#include <functional> //`std::hash`
#include <random> //`std::mt19937`
#include <iostream> //`std::cout`

static std::mt19937 rng;

static void seed(uint32_t seed) {
    rng.seed(static_cast<std::mt19937::result_type>(seed));
}
static void seed() {
    uint32_t t = static_cast<uint32_t>( time(nullptr) );
    std::hash<uint32_t> hasher; size_t hashed=hasher(t);
    seed( static_cast<uint32_t>(hashed) );
}

int main(int /*argc*/, char* /*argv*/[]) {
    seed();
    std::uniform_int_distribution<> dis(0, 5);
    std::cout << dis(rng);
}