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;
}
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
最後の注意点が1つあります/dev/urandom
。最新のOpenBSDにはありません。代わりに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;
}
ありがとう
FILE
バッファリングされた読み取りを使用することを指摘してくれたBen Voigtに特に感謝します。したがって、使用しないでください。
言及してくれたPeter Cordes getrandom
と、OpenBSDのの欠如にも感謝し/dev/urandom
ます。