Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

擬似乱数のシード生成方法の推奨に、一貫性がない #451

Closed
pegacorn opened this issue Jul 16, 2017 · 41 comments
Closed
Assignees

Comments

@pegacorn
Copy link

擬似乱数生成器の各テンプレートクラスのコンストラクタの説明ページに記載されているシードの説明と、標準乱数ライブラリの基本的な使い方に書いてあるシードの説明が矛盾しています。
標準乱数ライブラリの基本的な使い方に記載されている通り、現在時刻を使用するのはセキュリティ上問題があるので、標準乱数ライブラリの基本的な使い方の方に合わせた方が良いのではないでしょうか。

※ シード値には、初期状態の予測不可能性を高めるために、UNIX時間(エポックからの経過時間)や、非決定的な乱数を指定するのがよい

古典的な方法として、シード生成にはstd::time(NULL)の呼び出しによる、現在時間(エポックからの経過秒)が使われていた。この方法では、アプリケーションの起動時間から、シードが推測されてしまう可能性がある。シードが推測されれば、その後の乱数のシーケンスが推測されてしまい、セキュリティホールになる恐れがある。

@yumetodo
Copy link
Member

シード値について別ページにしてしまうべきかもしれません。random_deviceが信用できない実装も現実に存在するので・・・

@usagi
Copy link
Member

usagi commented Jul 16, 2017

@yumetodo さんの randome_device の信用できない実装云々は本当にあるのでこのチケットの提起理由からもシード値について別ページでまとめた方が良いという意見に同意します。

また、計算機で扱われる疑似乱数列はセキュリティーに対してのみ使われるのではなく、シード値により疑似乱数列を再現可能である事もシミュレーションへの応用では重要な事もあり、このチケットの関連する記事の編集時にはセキュリティーに偏重した記事になりすぎないように編集者には注意して頂ければ幸いです。

加えて、別ページでセキュリティーについて特に記述する際は暗号論的擬似乱数についての解説と mt19937 など現在のC++言語の標準ライブラリーの提供する擬似乱数では十分なセキュリティー要件を満たせない事も記述ないし、どこかよい解説へリンクするなど必要かもしれません。

@yumetodo
Copy link
Member

yumetodo commented Jul 16, 2017

現在のC++言語の標準ライブラリーの提供する擬似乱数では十分なセキュリティー要件を満たせない

それは初耳でした・・・、ちなみに参考ページなどありますか?

@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 16, 2017

mt19937 などの <random> 内の擬似乱数生成器は暗号論的に安全ではないので,そもそもセキュリティ用途に使うべきではありませんし,そういう用途を想定していないと思います.

MT が暗号論的に安全ではないことは松本眞先生の HP にも記載されています:

暗号用の乱数として使いたい.

この生成法は、計算量理論的に安全な乱数をそのままでは生成しません。 これは、MTを含めあらゆる線形漸化式に基づく生成法に言えることで、 十分な長さの出力列を見れば、その後の数列を完全に予言することが できます。

そのため、Secure Hashing Algorithm(数ワードを圧縮して1ワードを 生成する、非可逆的なアルゴリズム)と組み合わせて使う必要があります。 例えば、出力列を8ワードごとに切って、ハッシュ関数で 1ワードに圧縮して使う(結果、出力の長さは1/8になる)という工夫が 必要です。

@faithandbrave
Copy link
Member

faithandbrave commented Jul 16, 2017

あ、すいません。ぼくが書いたセキュリティ用途というのは、暗号論的という意図ではないです。
ポケモンの乱数調整のような、お金や個人情報といったものが絡まない、公平性を保つくらいの意図です。
その意図について、より適切な言葉を選択すべき、という話ならそうした方がいいと思います。

シード生成の話を個別ページに書くなら、リファレンスから外れる場合はarticle階層に記事として書くのがよいと思います。
random_deviceが十分な品質でない場合があるというのは、MinGWのバグによるものではないかと思いますが、それ以外にも具体的な例を挙げられる方はいますか?

@usagi
Copy link
Member

usagi commented Jul 16, 2017

  1. @yumetodo @e-kwsm : 議論と補足、ありがとうございます。
  2. @faithandbrave @yumetodo : シード問題の件、私からは具体的には mingw のそれだけです。

@yumetodo
Copy link
Member

私からは具体的には mingw のそれだけです。

私もmingwのあれだけですね

@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 16, 2017

あ、すいません。ぼくが書いたセキュリティ用途というのは、暗号論的という意図ではないです。
ポケモンの乱数調整のような、お金や個人情報といったものが絡まない、公平性を保つくらいの意図です。
その意図について、より適切な言葉を選択すべき、という話ならそうした方がいいと思います。

「セキュリティ用途」というのは語弊があるのでたとえば「シミュレーション用途」とした方がよいと思います.


random_seedが十分な品質でない場合があるというのは、MinGWのバグによるものではないかと思いますが、それ以外にも具体的な例を挙げられる方はいますか?

random_device は「実装の制限によって予測不能な乱数生成器を定義できない場合、このクラスは擬似乱数生成器で定義される可能性がある」と規格にあります.
GCC の random_devicemt19937 にフォールバックします (tr1/random.h).MinGW は /dev/urandom を利用できないからでしょう.

ちなみに clang の random_device は MSVS の暗号論的に安全な rand_s にフォールバックします (src/random.cpp).

他の環境については分かりません.


そもそもシミュレーションでシードを指定するのは結果を再現するためや,違うシードでも同じ結果を得られるか確認するためだと思います.

@faithandbrave
Copy link
Member

faithandbrave commented Jul 16, 2017

random_deviceのMinGW実装(libstdc++のWindows実装)は、2つの問題があります:

  1. 擬似乱数実装(mt19937)になる場合に、シードを指定する方法がないため、デフォルトシードが使用され、固定の乱数列が出力されてしまいます。これがMinGWのバグです。random_deviceの設計ミスとも言えるかもしれません
  2. 擬似乱数実装になることで、現実の「予測されない乱数列がほしい」という問題に対処できない、というものがあります。

random_deviceの既知の問題がそれであれば、シードについての記事よりも先に、random_deviceのページにそれを書いたほうがよいですね。

「セキュリティ」という言葉の代わりに「シミュレーション」という言葉を選択することが適切かは、ちょっと調べる必要がありますね。「再現性」という点についてであれば「シミュレーション」という分野に含まれているので問題ないですが、「ユーザーに(絶対にではないけど)予測されたくない」という用途が「シミュレーション」という分野またはその定義に含まれているのか、あるいはその用途が広く認知されているのか、という根拠が必要になると思います。もしどなたか提示できるのであれば、お願いします。
セキュリティとは程度問題だと考えています。守りたいものがあり、それがユーザー(あるいは能動的攻撃者)にどの程度で「これ以上、予測のためにがんばるのは無理」と思わせたいのかによって、擬似乱数で十分だったり、暗号論的乱数が必要だったりで選択的なものだと思います。
もし「セキュリティ」と「シミュレーション」どちらも言葉の選択として不適切あるいは十分でない場合には、どちらの言葉も使わない説明に直すことも考えられます。

現在の文章がこれで、

古典的な方法として、シード生成にはstd::time(NULL)の呼び出しによる、現在時間(エポックからの経過秒)が使われていた。この方法では、アプリケーションの起動時間から、シードが推測されてしまう可能性がある。 シードが推測されれば、その後の乱数のシーケンスが推測されてしまい、セキュリティホールになる恐れがある。

「セキュリティホール」という言葉を使用せずに、以下のような修正ができるでしょう:

古典的な方法として、シード生成にはstd::time(NULL)の呼び出しによる、現在時間(エポックからの経過秒)が使われていた。この方法では、アプリケーションの起動時間から、シードが推測されてしまう可能性がある。 シードが推測されれば、その後の乱数のシーケンスが推測されてしまう場合がある。乱数列を予測困難にしたい場合は、random_deviceを使用してシードを生成するのがよいだろう。

@yumetodo
Copy link
Member

いや、この際具体的な用途はぼかして、seedとして何を使うかと生成アルゴリズムに何を使うかに分けた上で安全性順に列挙して一つのページで取り上げるべきかと。random_deviceの実装バグについてはrandom_deviceにページに書いた上で、それを参照する方針で・・・

ちなみにseedを無駄にいろいろな方法で取得するコードを以前書いたことがあります。
https://github.com/YSRKEN/KanColleSimulator_KAI/blob/6d3e796838890601c756dea415bb05751d47cc6c/KCS_CUI/source/random.cpp

@faithandbrave
Copy link
Member

faithandbrave commented Jul 16, 2017

まず、シード生成について個別ページを用意することはかまいません。どなたか動機がある人が書いてください(私はとくに動機がないので書きません)。「書くべき」という意見だけでは書かれないので、誰も書かないなら書かないで終わるだけです。
その話はどなたかが担当をとってから、具体的に議論すべきです。

ここでいま議論すべきは、シード生成についての記事を書く・書かないに関わらず、概要ページで書く内容と、一貫性あるデフォルトのシード生成の方針、random_deviceのクラスページに注意事項としてなにを書くか、だと思います。

ひとまず、random_deviceについて、libc++のWindows実装が問題ないことはわかりました。libstdc++のWindows実装には少なくとも、注意事項を記載する必要があるでしょう。それについて、既知の問題が挙がりきったと判断した段階で、ぼくが書きます。
libstdc++は「固定の乱数列が生成され、そうしないための方法がない」と書きましたが、これは誤解でした。コンストラクタで与えるトークン文字列を数値が入った文字列にすれば、文字列から整数に変換されてシードとして使われるようですね。

@faithandbrave faithandbrave self-assigned this Jul 16, 2017
@faithandbrave faithandbrave changed the title 擬似乱数のシード 擬似乱数のシード生成方法の推奨に、一貫性がない Jul 16, 2017
@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 16, 2017

random_deviceの既知の問題がそれであれば、シードについての記事よりも先に、random_deviceのページにそれを書いたほうがよいですね。

random_device の注意点は

  • 実装によっては擬似乱数生成器を用いている
  • 実装が非決定論的な場合,/dev/urandom にアクセスするため遅い (手元で調べると mt19937_64 の 2 倍以上)

以上をふまえると, 擬似乱数生成器のシードに使うのにはいいですが,

現実の「予測されない乱数列がほしい」という問題

に使用するのは推奨されないと思います.


libstdc++は「固定の乱数列が生成され、そうしないための方法がない」と書きましたが、これは誤解でした。コンストラクタで与えるトークン文字列を数値が入った文字列にすれば、文字列から整数に変換されてシードとして使われるようですね。

random_device は理想的には「真の」乱数を返すので,実体が擬似乱数生成器の場合にシードを与えて使うのは本来の用途ではない (mt19937 等を直接使うべき) と思います.


「セキュリティ」という言葉の代わりに「シミュレーション」という言葉を選択することが適切かは、ちょっと調べる必要がありますね。「再現性」という点についてであれば「シミュレーション」という分野に含まれているので問題ないですが、「ユーザーに(絶対にではないけど)予測されたくない」という用途が「シミュレーション」という分野またはその定義に含まれているのか、あるいはその用途が広く認知されているのか、という根拠が必要になると思います。もしどなたか提示できるのであれば、お願いします。
セキュリティとは程度問題だと考えています。守りたいものがあり、それがユーザー(あるいは能動的攻撃者)にどの程度で「これ以上、予測のためにがんばるのは無理」と思わせたいのかによって、擬似乱数で十分だったり、暗号論的乱数が必要だったりで選択的なものだと思います。
もし「セキュリティ」と「シミュレーション」どちらも言葉の選択として不適切あるいは十分でない場合には、どちらの言葉も使わない説明に直すことも考えられます。

確かに程度問題ですが,mt19937 他をセキュリティ強度の高い用途に使用するのは不適です.
決定論的だが高速な乱数生成器が向いているのは多数の乱数を利用する Monte Carlo 法などのシミュレーションです.

あるいは

いや、この際具体的な用途はぼかして、

用途を明記しなくてもいいと思います.


まず、シード生成について個別ページを用意することはかまいません。どなたか動機がある人が書いてください(私はとくに動機がないので書きません)。

C++ 固有の問題ではないので書かなくてよいと思います.


@pegacorn さんの投稿に立ち返ると,

※ シード値には、初期状態の予測不可能性を高めるために、UNIX時間(エポックからの経過時間)や、非決定的な乱数を指定するのがよい

古典的な方法として、シード生成にはstd::time(NULL)の呼び出しによる、現在時間(エポックからの経過秒)が使われていた。この方法では、アプリケーションの起動時間から、シードが推測されてしまう可能性がある。シードが推測されれば、その後の乱数のシーケンスが推測されてしまい、セキュリティホールになる恐れがある。

について,乱数列の予測不可能性やセキュリティ強度を高めるのはシードが行うことではない (暗号論的に強い乱数生成法を使うべき) ので,
「一般的にシードにはUNIX時間(エポックからの経過時間)や非決定的な乱数が用いられる」
とするのがよいと思います.

@faithandbrave
Copy link
Member

faithandbrave commented Jul 18, 2017

擬似乱数生成器のシードに使うのにはいいですが,
現実の「予測されない乱数列がほしい」という問題
に使用するのは推奨されないと思います.

いえ、問題を知った上で使用する分には問題ないです。規格上の保証がないということが、実装を理解した上での用途を限定することにはならないです。
/dev/urandomは遅いと言っても、それがボトルネックになるコードを書かなければよいだけです。

random_device は理想的には「真の」乱数を返すので,実体が擬似乱数生成器の場合にシードを与えて使うのは本来の用途ではない (mt19937 等を直接使うべき) と思います.

workaroundを示したまでです。MinGW環境のrandom_deviceを使うべきでないのは変わらないです。

まず、シード生成について個別ページを用意することはかまいません。どなたか動機がある人が書いてください(私はとくに動機がないので書きません)。
C++ 固有の問題ではないので書かなくてよいと思います.

標準ライブラリの範囲内で、正しい選択を促す手助けはあってもよいと思います。ぼくの考えでは、それはリファレンスページに一文書くだけで済むと思うので、記事を起こすほどではないと思いますが。
その記事を書く上で必要なのは、標準ライブラリの更新に追従できる構成にすること (記事を書いた人以外が更新することを考慮すること)、時事的な記事になって古くなってはならない、ということですね。これはリファレンスサイトとしては運用が難しいものでもあるので、外部のユーザーブログなどに任せてリンクを貼る程度にした方が楽ではあります。

について,乱数列の予測不可能性やセキュリティ強度を高めるのはシードが行うことではない (暗号論的に強い乱数生成法を使うべき) ので,
「一般的にシードにはUNIX時間(エポックからの経過時間)や非決定的な乱数が用いられる」
とするのがよいと思います.

例としてrandom_deviceを使用しているのは、シード生成のデフォルトの方法を示すのが目的です。ですので、選択肢を示しておわりではなく、とくにそうしない理由がないならばこの方法を使いましょう、くらいの推奨としたいです。
「乱数列の予測不可能性やセキュリティ強度を高めるのはシードが行うことではない」というのが「シードだけで行うべきではない」であれば同意しますが、必ずしも暗号論的でないといけないとは思いません。これは先に言ったとおり、セキュリティというのが程度問題であるという意図からです。たとえばゲームの世界では、シードを非決定的な乱数で作り、擬似乱数生成器としてメルセンヌ・ツイスターやXorshiftを使う、というのは広くあたり前に行われている方式です。
予測可能性についても、線形合同法のように現在の乱数値が分かれば次の値が予測できてしまうものに比べて、メルセンヌ・ツイスターは過去の623個の乱数列が手に入らないと次の値を予測できません(生成された乱数を加工して使用していれば、生の乱数列を手に入れるのは困難となる)。この点について、乱数の予測可能性は、擬似乱数アルゴリズムでも考慮されていることです。
@e-kwsm さんは「セキュリティ」という言葉に過剰反応しているように見えます。その言葉を使わない例も示していますので、そちらもご検討ください。

@yumetodo
Copy link
Member

workaroundを示したまでです。MinGW環境のrandom_deviceを使うべきでないのは変わらないです。

とりあえずmingwの実装に対するワークアラウンドはWin32APIのRtlGenRandomを呼び出す方向でいいのでは?他の有名な実装はとりあえずrandom_deviceで。必要ならASLRが行われるOS上では適当な関数ポインタの値を利用するなり、Intel CPUならRDRAND/RDSEED命令を呼び出してもいいでしょうが、それはデフォルトの方法に書くものじゃないですし。これでSeedについてはいいでしょう。


擬似乱数列生成アルゴリズムに何を利用するかは、各擬似乱数生成クラスの解説ですでにある程度解説されているので問題があれば追記すれば良く、STLのそれが不満であれば同じインターフェースのクラスを自作すればいい、でFAでは。

@faithandbrave
Copy link
Member

faithandbrave commented Jul 19, 2017

ここで言うworkaroundは、MinGWのrandom_deviceで固定の乱数列にしない方法のことで、ユーザーがラップするとかの話ではないです。
ひとまず、random_deviceのMinGWの問題は共通認識になったと判断し、random_deviceの個別ページに問題を記載します。

@yumetodo
Copy link
Member

ところでrandom_deviceのlibstdc++の実装ですが、_GLIBCXX_X86_RDRANDがdefineされていてRDRND命令をサポートする場合、/dev/urandomではなく、RDRNDを利用するように思います。

https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/random.h
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B11/random.cc

@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 19, 2017

「エポックを乱数のシードにすると,起動時間を調整することでユーザーに悪用される虞がある」
という主張には同意しますし,ゲームの個体値等で非決定的な乱数を使用するのは大仰であり,そこは開発者の裁量に任せるというのもわかります.

シードについて,

古典的な方法として、シード生成にはstd::time(NULL)の呼び出しによる、現在時間(エポックからの経過秒)が使われていた。この方法では、アプリケーションの起動時間から、シードが推測されてしまう可能性がある。シードが推測されれば、その後の乱数のシーケンスが推測されてしまう場合がある。乱数列を予測困難にしたい場合は、random_deviceを使用してシードを生成するのがよいだろう。

ですが,

擬似乱数生成器は同じシードを与えると同じ乱数列を返すため、結果を再現することができる。

古典的な方法として、シードにはstd::time(NULL)で取得した現在時間 (エポックからの経過秒) が使われてきた。しかしながら、この方法ではアプリケーションの起動時間によってシードを調整され、乱数列を制御されてしまう。これを防ぐには、非決定論的な random_device (ただし実装は処理系定義なので詳細は項目を参照すること) や CPU の RDRAND 命令を使用してシードを生成するのがよいだろう。

とするのはどうでしょうか.(MinGW では RDRAND が使用できても mt19937 を使う?)
必要であればクラスの項に

この乱数生成器は暗号論的に安全ではない。

と追加してもいいと思います.


random_device の使用上の注意事項は問題ないと思います.


ところでrandom_deviceのlibstdc++の実装ですが、_GLIBCXX_X86_RDRANDがdefineされていてRDRND命令をサポートする場合、/dev/urandomではなく、RDRNDを利用するように思います。

GCC の Implementation Status に書いてありました.

26.5.6 [rand.device] The default token argument to the random_device constructor is "default". Other valid arguments are "/dev/random" and "/dev/urandom", which determine the character special file to read random bytes from. The "default" token will read bytes from a hardware RNG if available (currently this only supports the IA-32 RDRAND instruction) otherwise it is equivalent to "/dev/urandom".

@faithandbrave
Copy link
Member

なるほど。RDRANDを使用する関数は、immintrin.hで定義されるため、Windowsでも使えるのですね。修正内容は、 @e-kwsm さんのもので合意します。
擬似乱数生成器の個別ページに、「暗号論的ではない」という記述をするのも問題ないです。擬似乱数生成器のページには、周期、サイズ、パフォーマンスなどのトレードオフの項目を設けているので、トレードオフのひとつとして載せておくのは有用だと思います。

この修正は @e-kwsm さんにお願いしてもよいでしょうか?

@e-kwsm e-kwsm self-assigned this Jul 20, 2017
@yumetodo
Copy link
Member

yumetodo commented Jul 20, 2017

@e-kwsm
ちなみにご存知と思いますが、RDSEED命令もあります

Intel AMD
RDRAND Ivy Bridge(3rd generation Core i series) Bulldozer v4
RDSEED Broadwell(5th generation Core i series) Ryzen

ref:


MinGW ではRDRANDが使用できてもmt19937を使う?

はい、そのようですね。

e-kwsm added a commit that referenced this issue Jul 20, 2017
* 各処理系の実装を追加
e-kwsm added a commit that referenced this issue Jul 20, 2017
* 各処理系の有効なトークンを追加
e-kwsm added a commit that referenced this issue Jul 20, 2017
「何を指定しても同じ結果となる」というのは任意の文字列を与えても効果はない
ということだと思われるが,返す乱数は異なるはずなので語弊がある.
引数の効果については 21bcf38 で書いたので削除した.
e-kwsm added a commit that referenced this issue Jul 20, 2017
* 代替手段として `RDRAND`, `CryptGenRandom`, `RtlGenRandom` を追加
@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 20, 2017

ひとまず random_device を更新しました.

概要

Windows環境ではCryptGenRandom()関数のラッパーとして

について,多分 MSVS のことだと思うのですが,ソースはありますか?

公式資料は見つけられませんでしたが,Stack Overflow には RtlGenRandom を使うと書いてありました
(clang が使う rand_sRtlGenRandom に依存; VS に合わせてる?).


clang 等の random_device 実装のドキュメントをご存じの方は追加していただけると幸いです.

@faithandbrave
Copy link
Member

VCに関しては執筆当時に実装を見て記述しましたが、現在は環境がないのでわからないです。

@pegacorn
Copy link
Author

公式資料は見つけられませんでしたが,Stack Overflow には RtlGenRandom を使うと書いてありました

ドキュメントは見つけられませんでしたが、以下の実装を見るとVC++2015/2017でもStack Overflowに記載のVC++2010と同様にrand_sを呼んでいるようです。

  • VC++2015
    • Microsoft Visual Studio 14.0/VC/include/random
    • Microsoft Visual Studio 14.0/VC/crt/src/stl/xrngdev.cpp
  • VC++2017
    • Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.10.25017/include/random
    • Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.10.25017/crt/src/stl/xrngdev.cpp

@pegacorn
Copy link
Author

void operator()(const random_device&) = delete; | コピーコンストラクタ。コピー不可 | C++11

random_deviceのこの説明(関数名)は間違っています。
2c66137#diff-cc7e74897bf8fb9dd2f60f98632e0661R40 の修正前後共に間違っています(汗)

正:
random_device(const random_device&) = delete; | コピーコンストラクタ。コピー不可 | C++11
random_device& operator=(const random_device&) = delete; | 代入演算子。代入不可 | C++11

yumetodo added a commit that referenced this issue Jul 22, 2017
yumetodo added a commit that referenced this issue Jul 22, 2017
rand_s and CryptGenRandom is a wrapper function of RtlGenRandom.

ref:
- #451
@yumetodo
Copy link
Member

yumetodo commented Jul 22, 2017

@pegacorn 修正してみました。規格書を見る限り代入演算子の戻り値の型指定はvoidになっていました(N3337 §26.5.6/N4140 §26.5.6/N4659 §29.6.6 rand.device)。

e-kwsm added a commit that referenced this issue Jul 24, 2017
* random_device は rand_s を使用すると明記
* rand_s, RtlGenRandom, CryptGenRandom の説明追加
  (MinGW で上記を使えるかは不明)
e-kwsm added a commit that referenced this issue Jul 25, 2017
…rry_engine のコンストラクタからシード生成法を削除 (#451)

2049a37
@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 25, 2017

一通り書きました

@yumetodo
Copy link
Member

@e-kwsm

替わりに CryptGenRandom を使用することを推奨

むしろ逆ではないかという印象を受けるのですが(CryptGenRandomRtlGenRandomで実装されている)、どこか出典はありますか?

リリース版においては、実行ごとに擬似乱数のシードが異なることが求められる。

リリース版 #とは

@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 26, 2017

替わりに CryptGenRandom を使用することを推奨

むしろ逆ではないかという印象を受けるのですが(CryptGenRandomRtlGenRandomで実装されている)、どこか出典はありますか?

MSDN RtlGenRandom より

[The RtlGenRandom function is available for use in the operating systems specified in the Requirements section. It may be altered or unavailable in subsequent versions. Instead, use the CryptGenRandom function.]

リリース版においては、実行ごとに擬似乱数のシードが異なることが求められる。

リリース版 #とは

開発時にはシードを固定してテストを行うが,リリース版では実行ごとに乱数が変わった方がよい/変わるべき (例えばゲーム) という意味で書きました.

@yumetodo
Copy link
Member

MSDN RtlGenRandom より

ありがとうございます

開発時にはシードを固定してテストを行うが,リリース版では実行ごとに乱数が変わった方がよい/変わるべき (例えばゲーム) という意味で書きました.

なるほど。ただ何かもうすこし意図が伝わりやすい文言に変えたいですがなにかないかな・・・

@faithandbrave
Copy link
Member

ゲームでもランダムテストでも、開発中でも乱数列は変わったほうがよいですね。開発中にシードを記録しておくことで、問題が発生したときに記録しておいたシードで問題を再現させる、という運用になると思います。
ゲームでもランダムテストでも、と書いたのはあらゆる入力で正しく振る舞うこと、乱雑さがあることを、開発中に検証できなければならないためです。

@pegacorn
Copy link
Author

@e-kwsm
「擬似」乱数生成器(と非決定的な乱数生成器)の説明を読むと、世の中にある全ての擬似乱数生成器が暗号処理に向かないように読めましたが、そういう意図でしょうか?
mt19937(※1)は向かないと思いますが、予測不可能性を持つ擬似乱数生成器を使いシードを知られなければ暗号処理に使える、と私は認識しているのですが。

※1…C++11のその他の擬似乱数生成器については確認できていません。

@e-kwsm
Copy link
Contributor

e-kwsm commented Jul 31, 2017

ゲームでもランダムテストでも、開発中でも乱数列は変わったほうがよいですね。開発中にシードを記録しておくことで、問題が発生したときに記録しておいたシードで問題を再現させる、という運用になると思います。

シードを指定したり,ランダムなシードを使ったりを選べるのは便利ですが,テスト毎にシードが変わると,結果が変わった原因がシードなのかコード修正なのか分かりません.

私は,開発中のテストでシードを固定して以下を検証しています:

  1. 乱数が関与しない (と思う) 箇所を変更したとき,同じシードを与えると同じ結果を返す
  2. 乱数が関与する箇所を変更したとき,同じシードを与えると違う (より正しいと思われる) 結果を返す

違うシードでも同程度の値に収束するかの検証は大事ですが,通常それにはコストがかかる (例えばコインを投げて表が出る確率を計算する場合,投げる回数が少ないと当然値はばらつきます) ので,あまり頻繁にはできません.

ゲームでもランダムテストでも、と書いたのはあらゆる入力で正しく振る舞うこと、乱雑さがあることを、開発中に検証できなければならないためです。

「あらゆる」入力 (シードを含む?) に対して検証するのは非現実的なので,問題が発生したシードを使ってデバッグということになるかと思います.


「擬似」乱数生成器(と非決定的な乱数生成器)の説明を読むと、世の中にある全ての擬似乱数生成器が暗号処理に向かないようにも読めましたが、そういう意図でしょうか?
mt19937(※1)は向かないと思いますが、予測不可能性を持つ擬似乱数生成器を使いシードを知られなければ暗号処理に使える、と私は認識しているのですが。

記事では random が提供する擬似乱数生成器を想定して書きました.
私は暗号には詳しくないのですが,wikipedia によると暗号論的に安全な擬似乱数生成器はあるそうです.
あるいはハッシュ関数と組み合わせるといいと思います.

@faithandbrave
Copy link
Member

@pegacorn さんの書かれた懸念に関しては、以下のように文脈を限定する修正をすればよいのではないでしょうか。

C++標準ライブラリにおいて、 linear_congruential_engine を始めとする擬似乱数生成器は決定論的で、再現性を持つ(同じシードを与えれば同じ乱数列を返す)

「決定論的」と「非決定的」で書き方が対称になっていないのは意図したものでしょうか。

@faithandbrave
Copy link
Member

シードを指定したり,ランダムなシードを使ったりを選べるのは便利ですが,テスト毎にシードが変わると,結果が変わった原因がシードなのかコード修正なのか分かりません.

私は,開発中のテストでシードを固定して以下を検証しています:

  • 乱数が関与しない (と思う) 箇所を変更したとき,同じシードを与えると同じ結果を返す
  • 乱数が関与する箇所を変更したとき,同じシードを与えると違う (より正しいと思われる) 結果を返す
    違うシードでも同程度の値に収束するかの検証は大事ですが,通常それにはコストがかかる (例えばコインを投げて表が出る確率を計算する場合,投げる回数が少ないと当然値はばらつきます) ので,あまり頻繁にはできません.

これはランダムテストの文脈でしょうか。
ランダムテストでは通常、ひとつのテストを一回だけではなく、数百回など多くの回数を実行して検証します。
一度のテストで数百回も実行すれば、現時点での実装の強固さがわかり、コードの変更によってテストが失敗したら、コードの変更が原因であると疑いやすくなるでしょう。

例として、ランダムテストで有名なHaskell言語のQuickCheckライブラリを挙げましょう。このライブラリは他の多くの言語に移植され、広く使われています。
https://hackage.haskell.org/package/QuickCheck-2.10.0.1/docs/Test-QuickCheck.html
このライブラリでは、デフォルトでひとつのテストに100回ランダムな入力を与えてテストします。シードも毎回変わります。シードを固定することもできますが、それはあくまでも、テストに失敗したときの再現を目的としています。

@e-kwsm さんのおっしゃる検証方針もひとつのやり方だとは思いますが、それは個人的なものなのか、あるいは何らかのソフトウェア開発プロセスとして確立しているものでしょうか。

「あらゆる」入力 (シードを含む?) に対して検証するのは非現実的なので,問題が発生したシードを使ってデバッグということになるかと思います.

ランダムテストでは実際に、あらゆる入力を検証し、エラーになる値を見つけ出してそれが正しい入力か、受け付けてはならない入力かを判断して、コードをより強固にします。これは現実で実際に行われていることです。

e-kwsm added a commit that referenced this issue Aug 6, 2017
@e-kwsm
Copy link
Contributor

e-kwsm commented Aug 6, 2017

@pegacorn さんの書かれた懸念に関しては、以下のように文脈を限定する修正をすればよいのではないでしょうか。

C++標準ライブラリにおいて、 linear_congruential_engine を始めとする擬似乱数生成器は決定論的で、再現性を持つ(同じシードを与えれば同じ乱数列を返す)

書き方が悪かったのですが,「<random>linear_congruential_engine etc は暗号処理には向かない」という意図でコメントしました.
再現性を持つのは擬似乱数生成器の一般的な性質です.


これはランダムテストの文脈でしょうか。
ランダムテストでは通常、ひとつのテストを一回だけではなく、数百回など多くの回数を実行して検証します。
一度のテストで数百回も実行すれば、現時点での実装の強固さがわかり、コードの変更によってテストが失敗したら、コードの変更が原因であると疑いやすくなるでしょう。

ランダムテストの文脈ではないです.
多くの乱数を必要とするプログラム (Monte Carlo シミュレーション) について,ランダムテストで入力やシードを変えて検証するのは大変だと思うのですが,実際に行われているのでしょうか.

シードを変えてしまうと,結果が変わった原因がコードの修正なのかシードなのか区別することができません.

例として、ランダムテストで有名なHaskell言語のQuickCheckライブラリを挙げましょう。このライブラリは他の多くの言語に移植され、広く使われています。
https://hackage.haskell.org/package/QuickCheck-2.10.0.1/docs/Test-QuickCheck.html
このライブラリでは、デフォルトでひとつのテストに100回ランダムな入力を与えてテストします。シードも毎回変わります。シードを固定することもできますが、それはあくまでも、テストに失敗したときの再現を目的としています。

「毎回変わるシード」というのは,ランダムな入力を生成するための,プログラム外部のものですか?
私が言及しているシードは (テスト対象の) プログラム自身が内部的に使用するものです.


@e-kwsm さんのおっしゃる検証方針もひとつのやり方だとは思いますが、それは個人的なものなのか、あるいは何らかのソフトウェア開発プロセスとして確立しているものでしょうか。

個人的な方法なので,もし詳しい方がいれば教えていただけると幸いです.

@faithandbrave
Copy link
Member

ランダムテストの文脈ではないです.
多くの乱数を必要とするプログラム (Monte Carlo シミュレーション) について,ランダムテストで入力やシードを変えて検証するのは大変だと思うのですが,実際に行われているのでしょうか.

シードを変えてしまうと,結果が変わった原因がコードの修正なのかシードなのか区別することができません.

意見が衝突していていて二者間では合意に至れない状態になっていると思います。そのため、第三者意見として、私と @e-kwsm さん以外の例が必要となります。第三者意見は、文献 (インターネット上の記事、論文、書籍、オープンソースソフトウェア) などの収集によって得られるでしょう。

しかし、私が示したランダムテストの文脈においては、開発時にシードが変わることが求められます。これは、 @e-kwsm さんが書かれた「リリース版においては、実行ごとに擬似乱数のシードが異なることが求められる。」という文章に対する反論として成立しているように思います。そのため、開発時にもシードを固定せず変動させることが有用であり、リリース版のみに限られたものではないと言えるのではないかと思います。

それとは別に一点質問です。

シードを変えてしまうと,結果が変わった原因がコードの修正なのかシードなのか区別することができません.

シードを固定することは、ロジックの維持には有用だと思いますが、リリース版においてシードを変動させて動作することは、開発時に検証しないのでしょうか?開発版で動作するものが、必ずしもリリース版で正しく動作するものではなく、かつ開発時に検証していない動作をリリースしてはならないと思います。

「毎回変わるシード」というのは,ランダムな入力を生成するための,プログラム外部のものですか?
私が言及しているシードは (テスト対象の) プログラム自身が内部的に使用するものです.

QuickCheckの機能としては、プログラム外部からランダム入力を生成するためのシードです。しかし、入力を生成するのが外部なのか内部なのかは、大きな違いはないと考えています。テスト可能なプログラムを設計するためには、外部から入力を受けることが必要となるためです。たとえば、現在日時に依存したプログラムはテストしにくいため、現在日時をパラメータとしてとるよう設計します。ランダムな入力を受け付けるのも、それと同じです。

@faithandbrave
Copy link
Member

faithandbrave commented Aug 7, 2017

モンテカルロ・シミュレーション分野での、テクニックとしての記事:

ここでは、(オプションとして)固定シードを選択する場合の理由として、以下の2つを挙げています:

  • モデルを開発あるいは変更した場合に、モデル変更がシミュレーション結果にどのような影響を与えたかを可視化できる
  • 同じ結果が得られる

この記事は、 @e-kwsm さんのとっている手法が他所にとっても有用であると裏付けるものです。しかし、モンテカルロ・シミュレーションについては開発手法的な記事はなかなか見当たらないですね。
この記事も、ようやく見つけたもので、一例がやっとでした。

ここは、モンテカルロ・シミュレーションで開発時にシードを変動させることが必要かを調査するよりは、他分野においてそういったことが必要となっているかを調査して反例を示したほうがよさそうな気がします。ランダムテストの分野で私はそれを示したつもりですが、不足であればまた探します。

@e-kwsm
Copy link
Contributor

e-kwsm commented Aug 11, 2017

しかし、私が示したランダムテストの文脈においては、開発時にシードが変わることが求められます。これは、 @e-kwsm さんが書かれた「リリース版においては、実行ごとに擬似乱数のシードが異なることが求められる。」という文章に対する反論として成立しているように思います。そのため、開発時にもシードを固定せず変動させることが有用であり、リリース版のみに限られたものではないと言えるのではないかと思います。

「リリース版においては、実行ごとに擬似乱数のシードが異なることが求められる。」というのは当然の要件だと認識しています.また,この文言は開発中にシードをどうするか (固定するかどうか) を制限するものではありません.

それとは別に一点質問です。

シードを変えてしまうと,結果が変わった原因がコードの修正なのかシードなのか区別することができません.

シードを固定することは、ロジックの維持には有用だと思いますが、リリース版においてシードを変動させて動作することは、開発時に検証しないのでしょうか?開発版で動作するものが、必ずしもリリース版で正しく動作するものではなく、かつ開発時に検証していない動作をリリースしてはならないと思います。

簡単に言うと,私は Monte Carlo 法で多粒子系のシミュレーションをしています.結果が収束するまで時間がかかるので,通常のテストは短めに回して結果が一致するかどうか等を確かめています.
結果を解析するためにシミュレーションを回すとき(「リリース版」というと語弊がありますが)は異なるシードで複数本流して,同程度の値に収束することを確認しています.


記事ではテスト時のシードをどうするかを書いていませんが,この issue でどうすべきかを決めて書きますか?
どうするかはユーザーの判断に任せていいと私は思います.

@faithandbrave
Copy link
Member

faithandbrave commented Aug 11, 2017

ありがとうございます。 @e-kwsm さんの意図は理解しました。そのうえで、開発時のシードの決め方も、方針を示すくらいはしておこうかと思います。
@yumetodo さんからも「リリース版」という言葉の選択をどうにかしたい、と意見がでているため、ある程度合意がとれるところまではこのIssueで決めてしまってもよいのではないかと思います。

修正前:

リリース版においては、実行ごとに擬似乱数のシードが異なることが求められる。random_device クラスが非決定論的に実装されている環境ではこれを使用するのが望ましい。

修正後:

開発時には、検証目的でシードに固定値を設定することが必要とされる場合がある。それ以外の、実際に乱雑さが必要となる状況では、実行ごとに擬似乱数のシードが異なることが求められる。random_device クラスが非決定論的に実装されている環境では、これをシードとして使用するのが望ましい。

「リリース版」という表現をやめて、開発中の検証も考慮するよう修正しました。これを元に議論して、問題なさそうならコミットして終了としましょう。

@e-kwsm
Copy link
Contributor

e-kwsm commented Aug 12, 2017

記事

linear_congruential_engine を始めとする擬似乱数生成器は決定論的で、再現性を持つ(同じシードを与えれば同じ乱数列を返す)。この挙動は開発中のテストで有用である。

と書いたのと,「実際に乱雑さが必要となる状況」という表現が分かりにくい (特定のシードを与えることで乱数列に偏りが出ることは通常ない) ので,より簡潔に

実行ごとに異なる(多くの)乱数が必要な場合、random_device クラスが非決定論的に実装されている環境ではこれをシードとして、擬似乱数生成器を使用するとよい。

とするのが良いと思います.

@faithandbrave
Copy link
Member

それがよさそうですね。対応しておきます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants