当前位置: 首页 > 知识库问答 >
问题:

如何简洁、便携、彻底地将mt19937 PRNG种子化?

谭梓
2023-03-14

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

std::random_device rd;  
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);

通常这会取代某种“不神圣的可憎之物”,例如:

srand(time(NULL));
rand()%6;

std::random_device可以(有时)实现为具有固定种子的简单PRNG。因此,它可能在每次运行时产生相同的序列。(链接)这比time(NULL)还要糟糕。

更糟糕的是,复制和粘贴前面的代码段非常容易,尽管它们包含问题。一些解决方案需要获取大型图书馆,但这并不适合所有人。

有鉴于此,我的问题是如何在C++中简洁、可移植性和彻底地将mt19937 PRNG种子化?

    null

std::random_device::Entropy()不能很好地指示std::random_device可以执行或不可以执行的操作。

共有1个答案

谢墨竹
2023-03-14

我认为std::random_device的最大缺陷是,如果没有可用的CSPRNG,则允许它进行确定性回退。这本身就是不使用std::random_device种子PRNG的一个很好的理由,因为产生的字节可能是确定性的。不幸的是,它没有提供一个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;
}
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);

如果您愿意为安全性而牺牲简洁性,getrandom是Linux 3.17及以上版本和最近的Solaris上的一个极好的选择。getrandom的行为与/dev/urandom完全相同,只是如果内核在引导后还没有初始化它的CSPRNG,它就会阻塞。下面的片段检测LinuxGetRandom是否可用,如果不可用,则返回到/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没有/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

如果您需要加密安全的随机字节,您可能应该用POSIX的无缓冲的open/read/close替换fstream。这是因为basic_filebuffile都包含一个内部缓冲区,该缓冲区将通过标准分配器分配(因此不会从内存中擦除)。

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;
}

我还要感谢Peter Cordes提到getrandom,以及OpenBSD缺少/dev/urandom

 类似资料:
  • 问题内容: 下面的代码检查和是不同的值(变量,,只能有值,或),并且如果是这样,套到第三个字符: 能否以更简洁,可读和有效的方式做到这一点? 问题答案: 我假设您的代码中的三种情况之一成立。在这种情况下,该集合将由单个元素组成,由返回。 编辑: 正如Raymond Hettinger在评论中所建议的那样,您还可以使用元组拆包从集合中提取单个元素:

  • 本文向大家介绍Java彻底消灭if-else的8种方案,包括了Java彻底消灭if-else的8种方案的使用技巧和注意事项,需要的朋友参考一下 优化方案 1:提前 return,去除不必要的 else 如果 if-else 代码块包含 return 语句,可以考虑通过提前 return,把多余 else 干掉,使代码更加优雅。 优化前: 优化后: 优化方案 2:使用条件三目运算符 使用条件三目运算

  • 我最近在我的Macbook Pro上下载了Android Studio,每次打开它我都搞砸了。它给了我插件错误和其他几个错误。我需要从我的Mac上完全卸载它。我试着从我的mac上删除它,然后像你第一次那样重新安装它,但它什么也没有做,现在又出现了同样的问题。 我怎样才能把它完全移除并重新安装一个新的?

  • 可移植性是任何编程语言的重要方面。 众所周知,Rexx可用于各种操作系统,如Windows和Linux。 因此,需要确保在Windows平台上开发程序时,如果在Linux平台上运行相同的程序,则需要采取必要的预防措施。 Rexx能够运行系统级命令。 有些命令可用于了解运行它的操作系统是什么。 根据输出,它可以采取适当的操作来查看可以在此操作系统上运行的命令。 例子 (Example) 以下示例显示

  • 本文向大家介绍Android Studio彻底删除项目 Android Studio彻底删除Module,包括了Android Studio彻底删除项目 Android Studio彻底删除Module的使用技巧和注意事项,需要的朋友参考一下 Android Studio这样才能彻底删除项目,具体操作如下 1.Android Studio彻底删除Module 当不需要某个Module(工程)时,删

  • 我想从中使用docx4j从DOCX文件中提取文本,如下所示: 虽然存在“R::GetContent”和“R::GetRPR”,但我想知道为什么文本文档中不存在“R::GetText”。