namespace std {
class random_device;
}
概要
random_device
クラスは、非決定論的な乱数生成エンジンである。予測不能な乱数を生成することから、擬似乱数生成エンジンのシード初期化や、暗号化といった用途に使用できる。
random_device
の実装は処理系定義だが、Windows環境ではCryptGenRandom()
関数のラッパーとして、UNIX系環境では/dev/random
や/dev/urandom
から値を読み取る形で定義される場合がある。
実装の制限によって予測不能な乱数生成器を定義できない場合、このクラスは擬似乱数生成器で定義される可能性があるため、特にクロスプラットフォームなコードを書く場合は注意すること。
予測不能な乱数はソフトウェアでは実装できないため、これらはハードウェアのノイズやマウスの動きといった環境ノイズをエントロピープールとして乱数を生成する。 非決定論的な乱数生成器のパフォーマンスは擬似乱数生成器よりも悪く、特にエントロピープールが枯渇すると著しく悪化する。 再現性が必要なく、速度が遅くても問題ない状況で使用すること。
実装
- Windows
- UNIX 系
- Clang (libc++):
/dev/urandom
(デフォルト) または/dev/random
から値を読み取る - GCC (libstdc++): CPU の
RDRAND
命令を使う (デフォルト) か、/dev/urandom
(RDRAND
が使用できないときのデフォルト) または/dev/random
から値を読み取る
- Clang (libc++):
メンバ関数
構築
名前 | 説明 | 対応バージョン |
---|---|---|
(constructor) |
コンストラクタ | C++11 |
~random_device() = default; |
デストラクタ | C++11 |
void operator=(const random_device& ) = delete; |
代入演算子。代入不可 | C++11 |
生成
名前 | 説明 | 対応バージョン |
---|---|---|
operator() |
乱数を生成する | C++11 |
エンジンの特性
名前 | 説明 | 対応バージョン |
---|---|---|
entropy |
エントロピーを取得する | C++11 |
静的メンバ関数
生成の特徴
名前 | 説明 | 対応バージョン |
---|---|---|
min |
生成する範囲の最小値を取得する | C++11 |
max |
生成する範囲の最大値を取得する | C++11 |
メンバ型
型 | 説明 | 対応バージョン |
---|---|---|
result_type |
乱数生成結果の符号なし整数型 unsigned int 。 |
C++11 |
例
基本的な使い方
#include <iostream>
#include <random>
int main()
{
std::random_device rng;
for (int i = 0; i < 10; ++i) {
// 予測不能な乱数を生成する
unsigned int result = rng();
std::cout << result << std::endl;
}
}
xxxxxxxxxx
#include <iostream>
#include <random>
int main()
{
std::random_device rng;
for (int i = 0; i < 10; ++i) {
// 予測不能な乱数を生成する
unsigned int result = rng();
std::cout << result << std::endl;
}
}
出力例
770203506
3783995753
458506084
4033712415
4182902552
940753559
327526966
3226755811
4026482080
1445600541
パスワードを生成する
#include <iostream>
#include <cassert>
#include <string>
#include <random>
// candidate_chars : パスワードに含める文字の集合
// length : パスワードの長さ
std::string create_password(const std::string& candidate_chars, std::size_t length)
{
assert(!candidate_chars.empty());
// 非決定論的な乱数生成器を使用する
std::random_device engine;
// パスワード候補となる文字集合の範囲を一様分布させる
std::uniform_int_distribution<std::size_t> dist(0, candidate_chars.size() - 1);
std::string password;
for (std::size_t i = 0; i < length; ++i) {
// パスワード候補の文字集合から、ランダムな一文字を選択する
std::size_t random_index = dist(engine);
char random_char = candidate_chars[random_index];
// 選択した文字を、パスワード文字列に追加する
password += random_char;
}
return password;
}
int main()
{
std::string candidate_chars = "abcdefghijklmnopqrstuvwxyz";
std::size_t length = 8;
std::string password = create_password(candidate_chars, length);
std::cout << password << std::endl;
}
xxxxxxxxxx
#include <iostream>
#include <cassert>
#include <string>
#include <random>
// candidate_chars : パスワードに含める文字の集合
// length : パスワードの長さ
std::string create_password(const std::string& candidate_chars, std::size_t length)
{
assert(!candidate_chars.empty());
// 非決定論的な乱数生成器を使用する
std::random_device engine;
// パスワード候補となる文字集合の範囲を一様分布させる
std::uniform_int_distribution<std::size_t> dist(0, candidate_chars.size() - 1);
std::string password;
出力例
jyiasder
バージョン
言語
- C++11
処理系
- Clang: 3.2 ✅
- GCC: 4.7.2 ✅
- ICC: ??
- Visual C++: 2010 ✅, 2012 ✅, 2013 ✅, 2015 ✅, 2017 ✅
備考
MinGW GCC(libstdc++)
Windows版のGCC (MinGW, libstdc++) 9.1まででは、random_device
クラスは擬似乱数生成器であるmt19937
で実装されている。その環境のデフォルトでは固定の乱数列が生成されてしまうので注意すること。コンストラクタの引数としてシード値を文字列化して渡せばmt19937
のシードとして扱われるが、非決定論的な乱数として振る舞わないことは変わらない。この環境ではrandom_device
の使用は推奨しない
GCC (MinGW, libstdc++) 9.2からは、この問題は解決されている。PR libstdc++/85494 use rdseed and rand_s in std::random_deviceにより、暗号学的に安全で非決定論的な乱数を生成するrand_s
関数での実装になるためである。
代替
- クロスプラットフォーム
- CPU が提供する
RDRAND
,RDSEED
命令
- CPU が提供する
- Windows
rand_s
(CryptGenRandom
のラッパー)CryptGenRandom
関数(Windows XP/Windows Server 2003以降。非推奨)BCryptGenRandom
関数(Windows Vista/Windows Server 2008以降)
Workaround
ワークアラウンドとして次のコードが利用できる。rand_s
ではなくCryptGenRandom
を用いているのは、rand_s
を利用するにはWindows.h
をincludeする前に_CRT_RAND_S
のdefineが必要でworkaroundには向かないため。またBCryptGenRandom
を用いていないのは、対象としている環境ではCryptGenRandom
は利用可能であり、BCryptGenRandom
より使える環境が広いためである。
//! @file random_device.hpp
#pragma once
#include <random>
#if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <2))
#include <system_error>
#include <limits>
#include <string>
#include <Windows.h>
#include <wincrypt.h>
namespace workaround_mingw_gcc {
class random_device {
private:
class crypt_context {
private:
HCRYPTPROV prov_;
public:
crypt_context(DWORD prov_type, LPCTSTR container = nullptr, LPCTSTR provider = nullptr, DWORD flags = 0) {
const auto success = ::CryptAcquireContext(&this->prov_, container, provider, prov_type, flags);
if (!success) {
throw std::system_error(
std::error_code(::GetLastError(), std::system_category()),
"CryptAcquireContext:(" + std::to_string(success) + ')'
);
}
}
crypt_context(const crypt_context&) = delete;
void operator=(const crypt_context&) = delete;
~crypt_context() noexcept {
::CryptReleaseContext(this->prov_, 0);
}
//HCRYPTPROV& get() noexcept { return this->prov_; }
const HCRYPTPROV& get() const noexcept { return this->prov_; }
};
crypt_context prov_;
public:
using result_type = unsigned int;
explicit random_device(const std::string& /*token*/ = "workaround_mingw_gcc ")
: prov_(PROV_RSA_FULL)
{}
random_device(const random_device&) = delete;
void operator=(const random_device&) = delete;
//~random_device() = default;
double entropy() const noexcept { return 0.0; }
result_type operator()() {
result_type re;
const auto success = ::CryptGenRandom(this->prov_.get(), sizeof(re), reinterpret_cast<BYTE*>(&re));
if (!success) {
throw std::system_error(
std::error_code(::GetLastError(), std::system_category()),
"CryptGenRandom:(" + std::to_string(success) + ')'
);
}
return re;
}
static constexpr result_type min() { return std::numeric_limits<result_type>::min(); }
static constexpr result_type max() { return std::numeric_limits<result_type>::max(); }
};
} // namespace workaround_mingw_gcc
namespace cpprefjp {
using workaround_mingw_gcc::random_device;
}
#else //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
namespace cpprefjp {
using std::random_device;
}
#endif //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
使用例は次の通り。上記コードをrandom_device.hpp
というファイル名で保存していると仮定する。cpprefjp::random_device
がstd::random_device
のworkaroundで、C++11標準規格の要求を満たしたクラス。
#include "random_device.hpp"
#include <iostream>
#include <array>
#include <functional>
#include <algorithm>
using seed_v_t = std::array<cpprefjp::random_device::result_type, sizeof(std::mt19937)/sizeof(cpprefjp::random_device::result_type)>;
seed_v_t create_seed_v()
{
cpprefjp::random_device rnd;
seed_v_t sed_v;
std::generate(sed_v.begin(), sed_v.end(), std::ref(rnd));
return sed_v;
}
std::mt19937 create_random_engine()
{
const auto sed_v = create_seed_v();
std::seed_seq seq(sed_v.begin(), sed_v.end());
return std::mt19937(seq);
}
std::mt19937& random_engine()
{
static thread_local std::mt19937 engine = create_random_engine();
return engine;
}
int main()
{
std::mt19937& engine = random_engine();
std::uniform_int_distribution<int> dist(1, 32);
for(int i = 0; i < 10; ++i) std::cout << dist(engine) << std::endl;
}
参照
- GCC: Implementation Status 26.5.6 [rand.device]
- Microdoft Visual Studio 2017: random_device Class
- /dev/random - Wikipedia
- Man page of RANDOM
- CryptGenRandom function (wincrypt.h) - Win32 apps | Microsoft Docs
- random_deviceの実装(再訪) - 煙人計画
- Replacing
/dev/urandom
May 4, 2016 - Security - gccをwindowsで使うならstd::random_deviceを使ってはいけない - Qiita
- MSC30-C. 擬似乱数の生成に rand() 関数を使用しない
CryptGenRandom
のエントロピー源(2005年時点): Cryptographically Secure Random number on Windows without using CryptoAPI – Michael Howard's Web Log