首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何简洁地、可移植地、彻底地播种mt19937 PRNG?

如何简洁地、可移植地、彻底地播种mt19937 PRNG?
EN

Stack Overflow用户
提问于 2017-07-13 07:44:19
回答 8查看 14.7K关注 0票数 120

我似乎看到了很多答案,其中有人建议使用<random>生成随机数,通常伴随着这样的代码:

代码语言:javascript
复制
std::random_device rd;  
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);

通常这会取代一些“令人讨厌的东西”,比如:

代码语言:javascript
复制
srand(time(NULL));
rand()%6;

我们可以通过争论time(NULL)提供的低熵,time(NULL)是可预测的,并且最终结果是不一致的来criticize旧的方式。

但所有这一切都适用于新的方式:它只是有了一个更闪亮的外表。

  • rd()返回单个unsigned int。它至少有16位,可能有32位。这不足以为MT的19937位状态设定种子。使用std::mt19937 gen(rd());gen()
  • (用32位设定种子并查看第一个输出)不能给出良好的输出分布。7和13永远不会是第一个输出。两个种子产生0。十二粒种子产生1226181350粒。(Link)
  • std::random_device可以被实现为一个简单的带有固定种子的PRNG。因此,它可能会在每次运行时产生相同的序列。(Link)这比time(NULL).

还要糟糕

更糟糕的是,复制和粘贴上述代码片段非常容易,尽管它们包含问题。这个问题的一些解决方案需要获取largish libraries,这可能并不适合每个人。

有鉴于此,我的问题是如何在C++中简洁、便携、彻底地播种mt19937 PRNG?

考虑到上面的问题,一个好的答案是:

作为mt19937/mt19937_64.

  • Cannot的来源,entropy.

  • Should必须完全植入libaries.

  • Should
  • 必须完全依赖std::random_devicetime(NULL),而不能依赖Boost或其他适合于少量行的entropy.
  • Should,这样复制粘贴到答案中会很好看。

Thoughts

  • 我目前的想法是,std::random_device的输出可以与time(NULL)、从address space randomization派生的值和硬编码的常量(可以在分发过程中设置)混合在一起(可能是通过异或),以在entropy.
  • std::random_device::entropy() does not中获得尽力而为的结果,这很好地指示了std::random_device可能会做什么或不会做什么。
EN

回答 8

Stack Overflow用户

发布于 2017-07-13 08:05:31

我认为std::random_device最大的缺陷是,如果没有可用的CSPRNG,它就会被允许进行确定性的后备。这本身就是不使用std::random_device作为PRNG种子的一个很好的理由,因为产生的字节可能是确定性的。不幸的是,它没有提供API来找出何时发生这种情况,或者请求失败,而不是低质量的随机数。

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

视窗

你可以依靠CryptGenRandom,一个CSPRNG。例如,您可以使用以下代码:

代码语言:javascript
复制
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的系统上存在)。

代码语言:javascript
复制
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)。

代码语言:javascript
复制
std::uint_least32_t seed;    
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);

与Boost的比较

在快速浏览source code之后,我们可以看到与boost::random_device (一个真正的CSPRNG)的相似之处。Boost在Windows上使用MS_DEF_PROV,这是PROV_RSA_FULL的提供程序类型。唯一缺少的就是验证加密上下文,这可以用CRYPT_VERIFYCONTEXT来完成。在*Nix上,Boost使用/dev/urandom。也就是说,这个解决方案是便携的,经过良好测试的,并且易于使用。

Linux专业化认证

如果您愿意牺牲简洁性来换取安全性,那么在Linux3.17和更高版本以及最近的Solaris上,getrandom是一个很好的选择。getrandom的行为与/dev/urandom相同,不同之处在于,如果内核在引导后尚未初始化它的CSPRNG,则它会阻塞。下面的代码片段检测Linux getrandom是否可用,如果不可用,则返回到/dev/urandom

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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的无缓冲的open/read/close。这是因为basic_filebufFILE都包含一个内部缓冲区,该缓冲区将通过标准分配器分配(因此不会从内存中擦除)。

这可以通过将sysrandom更改为:

代码语言:javascript
复制
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

票数 59
EN

Stack Overflow用户

发布于 2017-07-13 07:51:57

在某种意义上,这不是可移植的。也就是说,可以设想一个有效的、完全确定的运行C++的平台(例如,一个模拟器,它确定性地步进机器时钟,并具有“确定的”I/O),其中没有随机性来源来播种PRNG。

票数 23
EN

Stack Overflow用户

发布于 2017-07-13 18:52:52

您可以使用std::seed_seq,并使用Alexander Huszagh获取熵的方法将其填充到生成器所需的状态大小:

代码语言:javascript
复制
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);
}

如果有一种适当的方法来填充或从标准库中的UniformRandomBitGenerator创建SeedSequence,那么使用std::random_device进行正确的种子设定将会简单得多。

票数 15
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45069219

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档