我认为最大的缺点std::random_device
是,如果没有CSPRNG,则允许确定性回退。仅此一个很好的理由,不要使用来播种PRNG std::random_device
,因为产生的字节可能是确定的。不幸的是,它没有提供API来找出发生这种情况的时间,也没有提供请求失败的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<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;
}
像Unix一样
在许多类似Unix的系统上,应尽可能使用/ dev / urandom(尽管不保证此格式在POSIX兼容系统上也存在)。
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;
}
其他
如果没有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,该解决方案是便携式的,经过测试的且易于使用的。
Linux专业化
如果您愿意为了安全而牺牲简洁性,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 <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
OpenBSD的
最后一个警告:现代OpenBSD没有/dev/urandom
。您应该改用getentropy。
#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
其他想法
如果需要加密安全的随机字节,则可能应将fstream替换为POSIX的无缓冲打开/读取/关闭。这是因为两者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
。