我似乎看到很多答案,有人建议使用它
来生成随机数,通常伴随着这样的代码:
std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 5); dis(gen);
通常这会取代某种"邪恶的憎恶",例如:
srand(time(NULL)); rand()%6;
我们可能会批评旧的方式,认为time(NULL)
提供低熵,time(NULL)
可预测,最终结果是不均匀的.
但所有这一切都适用于新的方式:它只有一个更光亮的贴面.
rd()
返回一个unsigned int
.这至少有16位,可能是32位.这还不足以为MT的19937位状态提供种子.
使用std::mt19937 gen(rd());gen()
(以32位播种并查看第一个输出)不能提供良好的输出分布.7和13永远不会是第一个输出.两粒种子产生0.十二粒种子产生1226181350.(链接)
std::random_device
可以(有时是)实现为具有固定种子的简单PRNG.因此,它可能在每次运行时产生相同的序列.(链接)这甚至比time(NULL)
.
更糟糕的是,尽管存在它们包含的问题,但复制和粘贴上述代码片段非常容易.对此的一些解决方案需要获得可能不适合每个人的大型 库.
鉴于此,我的问题是如何在C++中简洁,便携,彻底地播种mt19937 PRNG?
鉴于上述问题,一个很好的答案:
必须完全播种mt19937/mt19937_64.
不能单独依赖std::random_device
或time(NULL)
作为熵的来源.
不应该依赖Boost或其他图书馆.
应该适合少量的线条,这样看起来很好,可以复制粘贴到答案中.
思考
我目前的想法是,输出来自std::random_device
(可能通过XOR)time(NULL)
,从地址空间随机化得到的值,以及硬编码常量(可以在分配期间设置)以获得熵的最佳努力.
std::random_device::entropy()
没有很好地说明std::random_device
可能做什么或不做什么.
Alexander Hu.. 56
我认为最大的缺陷std::random_device
是,如果没有可用的CSPRNG,它将被允许确定性回退.仅这一点是不使用PRNG种子的一个很好的理由std::random_device
,因为产生的字节可能是确定性的.遗憾的是,它不提供API来查明何时发生这种情况,或者请求失败而不是低质量的随机数.
也就是说,没有完全可移植的解决方案:但是,有一种体面的,最小的方法.您可以使用CSPRNG(定义sysrandom
如下)的最小包装来为PRNG播种.
您可以信赖CryptGenRandom
CSPRNG.例如,您可以使用以下代码:
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(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; }
在许多类Unix系统上,应尽可能使用/ dev/urandom(尽管不能保证在POSIX兼容系统上存在).
size_t sysrandom(void* dst, size_t dstlen) { char* buffer = reinterpret_cast(dst); std::ifstream stream("/dev/urandom", std::ios_base::binary | std::ios_base::in); stream.read(buffer, dstlen); return dstlen; }
如果没有可用的CSPRNG,您可以选择依赖std::random_device
.但是,如果可能的话,我会避免这种情况,因为各种编译器(最值得注意的是,MinGW)将它作为PRNG实现(实际上,每次产生相同的序列以提醒人们它不是随机的).
现在我们有了最小的开销,我们可以生成所需的随机熵位来为我们的PRNG播种.该示例使用(显然不足)32位来为PRNG播种,您应该增加此值(这取决于您的CSPRNG).
std::uint_least32_t seed; sysrandom(&seed, sizeof(seed)); std::mt19937 gen(seed);
在快速查看源代码之后,我们可以看到boost :: random_device(一个真正的CSPRNG)的相似之处.Boost MS_DEF_PROV
在Windows上使用,它是提供者类型PROV_RSA_FULL
.唯一缺少的就是验证加密上下文,这可以完成CRYPT_VERIFYCONTEXT
.在*Nix上,Boost使用/dev/urandom
.IE,这个解决方案是便携式的,经过良好测试,易于使用.
如果您愿意牺牲简洁性来保证安全性,getrandom
那么在Linux 3.17及更高版本以及最近的Solaris上是一个很好的选择.getrandom
行为完全相同/dev/urandom
,除非它在启动后内核尚未初始化其CSPRNG时阻塞.以下代码段检测Linux getrandom
是否可用,如果没有,则回退到/dev/urandom
.
#if defined(__linux__) || defined(linux) || defined(__linux) # // Check the kernel version. `getrandom` is only Linux 3.17 and above. # include# 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 # include 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
最后一个警告:现代OpenBSD没有/dev/urandom
.你应该使用getentropy.
#if defined(__OpenBSD__) # define HAVE_GETENTROPY #endif #if defined(HAVE_GETENTROPY) # includesize_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
如果您需要加密安全的随机字节,您应该用POSIX的无缓冲打开/读取/关闭替换fstream.这是因为两者basic_filebuf
并FILE
包含一个内部缓冲器,这将通过标准的分配器被分配(因此不从存储器擦拭).
这可以通过更改sysrandom
为:
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; }
特别感谢Ben Voigt指出FILE
使用缓冲读取,因此不应使用.
我还要感谢Peter Cordes的提及getrandom
,以及OpenBSD的缺乏/dev/urandom
.
我认为最大的缺陷std::random_device
是,如果没有可用的CSPRNG,它将被允许确定性回退.仅这一点是不使用PRNG种子的一个很好的理由std::random_device
,因为产生的字节可能是确定性的.遗憾的是,它不提供API来查明何时发生这种情况,或者请求失败而不是低质量的随机数.
也就是说,没有完全可移植的解决方案:但是,有一种体面的,最小的方法.您可以使用CSPRNG(定义sysrandom
如下)的最小包装来为PRNG播种.
您可以信赖CryptGenRandom
CSPRNG.例如,您可以使用以下代码:
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(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; }
在许多类Unix系统上,应尽可能使用/ dev/urandom(尽管不能保证在POSIX兼容系统上存在).
size_t sysrandom(void* dst, size_t dstlen) { char* buffer = reinterpret_cast(dst); std::ifstream stream("/dev/urandom", std::ios_base::binary | std::ios_base::in); stream.read(buffer, dstlen); return dstlen; }
如果没有可用的CSPRNG,您可以选择依赖std::random_device
.但是,如果可能的话,我会避免这种情况,因为各种编译器(最值得注意的是,MinGW)将它作为PRNG实现(实际上,每次产生相同的序列以提醒人们它不是随机的).
现在我们有了最小的开销,我们可以生成所需的随机熵位来为我们的PRNG播种.该示例使用(显然不足)32位来为PRNG播种,您应该增加此值(这取决于您的CSPRNG).
std::uint_least32_t seed; sysrandom(&seed, sizeof(seed)); std::mt19937 gen(seed);
在快速查看源代码之后,我们可以看到boost :: random_device(一个真正的CSPRNG)的相似之处.Boost MS_DEF_PROV
在Windows上使用,它是提供者类型PROV_RSA_FULL
.唯一缺少的就是验证加密上下文,这可以完成CRYPT_VERIFYCONTEXT
.在*Nix上,Boost使用/dev/urandom
.IE,这个解决方案是便携式的,经过良好测试,易于使用.
如果您愿意牺牲简洁性来保证安全性,getrandom
那么在Linux 3.17及更高版本以及最近的Solaris上是一个很好的选择.getrandom
行为完全相同/dev/urandom
,除非它在启动后内核尚未初始化其CSPRNG时阻塞.以下代码段检测Linux getrandom
是否可用,如果没有,则回退到/dev/urandom
.
#if defined(__linux__) || defined(linux) || defined(__linux) # // Check the kernel version. `getrandom` is only Linux 3.17 and above. # include# 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 # include 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
最后一个警告:现代OpenBSD没有/dev/urandom
.你应该使用getentropy.
#if defined(__OpenBSD__) # define HAVE_GETENTROPY #endif #if defined(HAVE_GETENTROPY) # includesize_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
如果您需要加密安全的随机字节,您应该用POSIX的无缓冲打开/读取/关闭替换fstream.这是因为两者basic_filebuf
并FILE
包含一个内部缓冲器,这将通过标准的分配器被分配(因此不从存储器擦拭).
这可以通过更改sysrandom
为:
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; }
特别感谢Ben Voigt指出FILE
使用缓冲读取,因此不应使用.
我还要感谢Peter Cordes的提及getrandom
,以及OpenBSD的缺乏/dev/urandom
.
从某种意义上说,这不可能是便携式的.也就是说,人们可以设想一个运行C++的有效的完全确定性平台(例如,一个模拟器,它确定性地处理机器时钟,并且具有"确定的"I/O),其中没有随机性来源PRNG.
您可以使用a std::seed_seq
并使用Alexander Huszagh的获取熵的方法将其填充到生成器的至少所需状态大小:
size_t sysrandom(void* dst, size_t dstlen); //from Alexander Huszagh answer above void foo(){ std::uint_fast32_t[std::mt19937::state_size] state; sysrandom(state, sizeof(state)); std::seed_seq s(std::begin(state), std::end(state)); std::mt19937 g; g.seed(s); }
如果有填充或创建一个适当的方式SeedSequence从UniformRandomBitGenerator在使用标准库std::random_device
播种正确就会简单得多.