System.Security.Cryptography.RandomNumberGeneratorの代わりにC#クラスSystem.Randomを使用するのはなぜですか?


87

System.Security.Cryptography.RandomNumberGenerator(またはRandomNumberGeneratorは抽象的であるため、そのサブクラス)の暗号的に安全な乱数ジェネレーターを常に使用するのではなく、System.Randomの「標準」乱数ジェネレーターを使用するのはなぜですか?

ネイトローソンは彼のGoogleTechTalkプレゼンテーションで私たちに語っています " 13:11分の CryptoStrikesBack」で、Python、Java、C#の「標準」乱数ジェネレーターを使用せず、代わりに暗号的に安全なバージョンを使用するます。

2つのバージョンの乱数ジェネレーターの違いを知っています(質問101337を参照))。

しかし、安全な乱数ジェネレーターを常に使用するとは限らない理由は何ですか?System.Randomを使用する理由 おそらくパフォーマンス?


7
どちらを入力しますか?
マチャ

13
あまりにも多くの人々がそれを彼らがしていることの正当化として真剣に使用しています(通常は大声ではありません)。コードは書かれている以上に読まれますが、些細な長さの違いを気にする人はいますか?
Mark Sowul 2012年

3
しかし、とにかく、暗号化を行っていないのに、なぜ暗号化RNGを使用する必要があるのでしょうか。
Mark Sowul 2012年

3
@Macha、それがエイリアスの目的です->using R = System.Security.Cryptography.RandomNumberGenerator; R.Create();
cchamberlain 2015

回答:


147

スピードと意図。乱数を生成していてセキュリティの必要がない場合、なぜ遅い暗号化関数を使用するのですか?あなたはセキュリティを必要としないのに、なぜそれが安全でないのにその番号が何か安全なものに使われるかもしれないと誰かに思わせるのですか?


31
私は意図的な議論がとても好きです。
Lernkurve 2009

12
Random.GetNextは、特にスレッド環境では、スペクトル全体に乱数を「拡散」するのが得意ではないことに注意してください。Rand5の問題からRand7のさまざまなソリューションをテストするプログラムを作成しているときに、この問題に遭遇しました。ちょうど今、0から10までの100000個の乱数のクイックスレッドテストでは、生成された数の82470が0でした。以前のテストでも同様の不一致が見られました。暗号化ランダムは、その数の分布において非常に均一です。レッスンは、常にランダムデータをテストして、ニーズに対して「十分にランダム」であることを確認することだと思います。
クリストファーL

35
@Kristofferあなたは誤用したと思いますRandom。推測させてください。Randomクラスの新しいインスタンスを番号ごとに作成しました。これは、粗いタイマーによってシードされるため、約1〜16ミリ秒の間隔で同じ値がシードされます。
CodesInChaos 2011年

15
@CodesInChaos:それに加えて、Random同じオブジェクトが複数のスレッドから使​​用された場合にすべて0を返す競合状態があります。
BlueRaja-Danny Pflughoeft 2012年

3
@KristofferL:上記のコメントを参照してください。この回答
BlueRaja – Danny Pflughoeft 2012年

66

速度とより便利なインターフェース(NextDouble()など)とは別に、固定シード値を使用して繰り返し可能なランダムシーケンスを作成することもできます。これは、とりわけテスト中に非常に役立ちます。

Random gen1 = new Random();     // auto seeded by the clock
Random gen2 = new Random(0);    // Next(10) always yields 7,8,7,5,2,....

2
そして、理解しやすいかもしれないBitConverter.ToInt32(Byte [] value、int startIndex)があります。;)
2009年

7
IanBellとDavidBrabenは、コンピューターゲームEliteでランダムジェネレーターを使用して、非常に限られたメモリで、惑星とその属性(サイズなど)の膨大なリストを作成しました。これは、ジェネレーターが(シードから)決定論的なパターンを作成することにも依存しています-Cryptoのものは明らかに(設計上)提供していません。それらがどのようにそれを行ったかについての詳細はここにあります: wiki.alioth.net/index.php / Random_number_generator と本「InfiniteGameUniverse:Mathematical Techniques」ISBN:1584500581には、そのような手法に関するより一般的な議論があります。
Daniel James Bryars 2010

7
MSDNは、このプロパティが.NETバージョン間で保持されることを保証しないことに注意してください。「Randomクラスでの乱数ジェネレーターの実装は、.NETFrameworkのメジャーバージョン間で同じままであるとは限りません。」
ローマンスターコフ2014年

2
@phoog 「その結果、アプリケーションコードは、同じシードが異なるバージョンの.NETFrameworkで同じ疑似ランダムシーケンスになると想定してはなりません。」-わからない、私にはかなりはっきりしているようだ。ただし、この警告にもかかわらず、既存のプログラムを壊さずに実際に変更できない場合でも、私は驚かないでしょう。
ローマンスターコフ2014

2
@phoog:あなたは一つのことを言っていて、それから正反対のことを言っています。あなたは自分自身と直接矛盾しています。
Timwi 2014

54

まず、リンクしたプレゼンテーションでは、セキュリティ上の理由から乱数についてのみ説明しています。したがって、Randomセキュリティ以外の目的で悪いとは主張していません。

しかし、私はそうだと主張します。の.net4実装にRandomは、いくつかの点で欠陥があります。乱数の品質を気にしない場合にのみ使用することをお勧めします。より良いサードパーティの実装を使用することをお勧めします。

欠陥1:播種

デフォルトのコンストラクターは、現在の時刻をシードします。したがってRandom、短い時間枠(約10ミリ秒)内にデフォルトのコンストラクターで作成されたすべてのインスタンスは、同じシーケンスを返します。これは文書化されており、「設計による」ものです。これは、コードをマルチスレッド化する場合に特に厄介ですRandom。各スレッドの実行の開始時にのインスタンスを単純に作成することはできないためです。

回避策は、デフォルトコンストラクターを使用する場合は特に注意し、必要に応じて手動でシードすることです。

ここでのもう1つの問題は、シードスペースがかなり小さい(31ビット)ことです。したがって、Random完全にランダムなシードを使用しての50kインスタンスを生成する場合、おそらく1つの乱数シーケンスを2回取得します(誕生日のパラドックスのため)。したがって、手動でシードすることも簡単ではありません。

欠陥2:によって返される乱数の分布にNext(int maxValue)偏りがある

対象となるパラメータがあります Next(int maxValue)明らかに均一はない。たとえば、計算r.Next(1431655765) % 2すると0、サンプルの約2/3が得られます。(回答の最後にあるサンプルコード。)

欠陥3:このNextBytes()方法は非効率的です。

のバイトあたりのコストはNextBytes()、で完全な整数のサンプルを生成するコストとほぼ同じNext()です。このことから、実際にバイトごとに1つのサンプルが作成されていると思います。

各サンプルから3バイトを使用するより良い実装ではNextBytes()、ほぼ3倍高速化されます。

この欠陥のおかげで Random.NextBytes()System.Security.Cryptography.RNGCryptoServiceProvider.GetBytesで、私のマシン(Win7、Core i3 2600MHz)よりも約25%高速です。

誰かがソース/逆コンパイルされたバイトコードを調べた場合、ブラックボックス分析で見つけたよりもさらに多くの欠陥が見つかると確信しています。


コードサンプル

r.Next(0x55555555) % 2 強く偏っている:

Random r = new Random();
const int mod = 2;
int[] hist = new int[mod];
for(int i = 0; i < 10000000; i++)
{
    int num = r.Next(0x55555555);
    int num2 = num % 2;
    hist[num2]++;
}
for(int i=0;i<mod;i++)
    Console.WriteLine(hist[i]);

パフォーマンス:

byte[] bytes=new byte[8*1024];
var cr=new System.Security.Cryptography.RNGCryptoServiceProvider();
Random r=new Random();

// Random.NextBytes
for(int i=0;i<100000;i++)
{
    r.NextBytes(bytes);
}

//One sample per byte
for(int i=0;i<100000;i++)
{   
    for(int j=0;j<bytes.Length;j++)
      bytes[j]=(byte)r.Next();
}

//One sample per 3 bytes
for(int i=0;i<100000;i++)
{
    for(int j=0;j+2<bytes.Length;j+=3)
    {
        int num=r.Next();
        bytes[j+2]=(byte)(num>>16);   
        bytes[j+1]=(byte)(num>>8);
        bytes[j]=(byte)num;
    }
    //Yes I know I'm not handling the last few bytes, but that won't have a noticeable impact on performance
}

//Crypto
for(int i=0;i<100000;i++)
{
    cr.GetBytes(bytes);
}

1
興味深いことに、あなたの発見を確認することができます:私のマシンではNext(1431655765)もシードで2/3を与えます。1431655765の魔法は何ですか?どのようにしてこの番号にたどり着きましたか?
citykid 2014年

1
@citykid数値を16進数またはビットとして見てください。これRandomは、31ビット整数を指定された上限のある数値に変換するために使用される疑わしい方法から生じる魔法です。詳細を忘れてしまいましたが、のようなものですrandomValue * max / 2^{31}
CodesInChaos 2014

1431655765_10 = 1010101010101010101010101010101_2
ティムS.

6
うーん。では、Random for C#のどの実装を使用することをお勧めしますか?
Arsen Zahray 2014

1
Next()ここであなたが示した、の分布の不均一性である聖なる牛は、かなり壮観なバグであり、あなたが最初に発見を書いた6年後の今日でも存在しています。(ドキュメントでは「疑似乱数は有限の数のセットから等しい確率で選択される」と主張しているため単に「欠陥」ではなく「バグ」と言います。そうではなく、ここのコードで証明されています。)
マークアメリー2017

24

System.Randomは、暗号的に安全な乱数を生成しないため、パフォーマンスが大幅に向上します。

4バイトのバッファーをランダムデータで1,000,000回満たす私のマシンでの簡単なテストは、ランダムの場合は49ミリ秒かかりますが、RNGCryptoServiceProviderの場合は2845ミリ秒かかります。満たすバッファーのサイズを大きくすると、RNGCryptoServiceProviderのオーバーヘッドの関連性が低くなるため、差が狭くなることに注意してください。


2
実際のテストでデモンストレーションしていただきありがとうございます。
Lernkurve 2009

3
これは厳しいと思うかもしれませんが、ベンチマークのコードを含めずにパフォーマンスベンチマークの結果を投稿する場合は-1です。性能特性場合でもRandomとはRNGCryptoServiceProvider(すべてのために私は、彼らがきたかもしれません知っている)最後の8年間に変更されていない、私は十分なコードのベンチマークの結果を信用していないスタックオーバーフローに使用完全に壊れてベンチマークを見てきました公開されていません。
マークアメリー2017

21

最も明白な理由はすでに述べたので、ここにもっとあいまいな理由があります。暗号化PRNGは通常、「実際の」エントロピーで継続的に再シードする必要があります。したがって、CPRNGを頻繁に使用すると、システムのエントロピープールが使い果たされる可能性があります。これにより、(CPRNGの実装に応じて)システムが弱くなるか、攻撃者がCPRNGを予測できるようになります。そのエントロピープール(したがって、DoS攻撃の攻撃ベクトルになります)。

いずれにせよ、アプリケーションは、他のまったく関係のないアプリケーションの攻撃ベクトルになりました。これらのアプリケーションは、実際にはCPRNGの暗号化プロパティに大きく依存しています。

これは実際の現実の問題ですが、Linuxを実行しているヘッドレスサーバー(マウスやキーボード入力などのエントロピーソースがないため、当然エントロピープールがかなり小さい)で観察されています。このサーバーでは、アプリケーション/dev/randomがあらゆる種類のカーネルCPRNGを誤って使用します。正しい動作は、から小さなシード値を読み取り、/dev/urandomそれを使用して独自のPRNGをシードすることです。


エントロピーとエントロピー枯渇に関するウィキペディアの記事やその他のインターネットソースを読みましたが、よくわかりません。乱数ジェネレーターにシステム時間や空きバイト数などが供給されている場合、どうすればエントロピープールを使い果たすことができますか?他の人がそれを攻撃ベクトルとして使用して乱数を予測するにはどうすればよいですか?簡単な例を教えてください。おそらく、この議論はオフラインにする必要があります。en.wikipedia.org/wiki/Entropy_%28computing%29
Lernkurve 2009

3
システム時間は予測可能であるため、エントロピーソースではありません。空きバイト数はわかりませんが、高品質のエントロピーソースではないかと思います。攻撃者はサーバーにさらにリクエストを送信することで、空きバイト数を減らし、部分的に決定論的にする可能性があります。アプリケーションは、エントロピープールを使い果たすことにより、他のセキュリティクリティカルなアプリケーションにランダム性の低い乱数を使用させるか、エントロピーソースが補充されるまで待機するため、攻撃ベクトルになります。
quant_dev 2009年

たとえば、32ビットシードが供給される疑似ランダムジェネレータを使用している場合、ブルートフォース攻撃はかなり簡単になることがよくあります。64ビットシードでさえ、誕生日攻撃の対象となる可能性があります。しかし、種子がそれよりもはるかに大きくなると、リスクはまったくわかりません。出力の各バイトに対して128ビットの状態をブロック暗号化アルゴリズムに通してから下位8ビットを出力するランダムジェネレーターがある場合、連続した出力バイトのギグがあっても、攻撃者はどのようにして状態を推測できますか?暗号化アルゴリズム自体?
スーパーキャット2013年

11

オンラインカードゲームやロッターをプログラミングしている場合は、シーケンスを推測するのがほぼ不可能であることを確認する必要があります。ただし、たとえば、ユーザーにその日の見積もりを表示する場合は、セキュリティよりもパフォーマンスの方が重要です。


9

これについてはある程度議論されてきましたが、最終的には、パフォーマンスの問題はRNGを選択する際の二次的な考慮事項です。そこには膨大な数のRNGがあり、ほとんどのシステムRNGを構成する缶詰のLehmer LCGは、最高ではなく、必ずしも最速でもありません。古い、遅いシステムでは、それは優れた妥協案でした。その妥協は、最近ではめったに実際に関連していません。これは主に、A)すでに構築されており、この場合「車輪の再発明」を行う本当の理由がないため、およびB)大多数の人々がそれを使用する目的のために、現在のシステムに存続します。 「十分に良い」。

最終的に、RNGの選択はリスク/報酬の比率に帰着します。ビデオゲームなどの一部のアプリケーションでは、リスクはまったくありません。Lehmer RNGは十分すぎるほどであり、小さく、簡潔で、高速で、よく理解されており、「箱の中に」あります。

たとえば、アプリケーションがオンラインポーカーゲームや宝くじで、実際の賞品が関係していて、方程式のある時点で実際のお金が使われる場合、「ボックス内」のレーマーはもはや適切ではありません。32ビットバージョンでは、最大でサイクル開始する前に、有効な状態が2 ^ 32しかない。最近では、それはブルートフォース攻撃への開かれた扉です。このような場合、開発者はいくつかの種の非常に長い期間のRNGのようなものに行き、おそらく暗号的に強力なプロバイダーからシードしたいと思うでしょう。これにより、速度とセキュリティの間で適切な妥協点が得られます。そのような場合、その人はメルセンヌツイスター複数の再帰ジェネレーターのようなものを探しに出かけるでしょうある種のの。

アプリケーションがネットワークを介して大量の財務情報を通信するようなものである場合、今では大きなリスクがあり、考えられる報酬を大幅に上回ります。時には重武装の男性が適切な唯一のセキュリティであるため、装甲車はまだあります。私を信じてください。戦車、戦闘機、ヘリコプターを備えた特殊部隊の旅団が経済的に実現可能であれば、それが選択の方法です。このような場合、暗号的に強力なRNGを使用することは理にかなっています。これは、取得できるセキュリティのレベルが何であれ、必要なレベルではないためです。だからあなたはあなたが見つけることができる限り多くを取るでしょう、そしてコストは時間またはお金のいずれかで非常に、非常に遠い2位の問題です。そして、それが非常に強力なコンピューターで生成するのにすべてのランダムシーケンスが3秒かかることを意味する場合は、3秒待つことになります。


3
私はあなたがあなたの大きさについて間違っていると思います。財務データの送信は非常に迅速である必要があります。取引アルゴリズムが競合他社よりも0.1ミリ秒早く結果に到達できる場合は、買い/売り/ストップロス/見積もりコマンドのキューに入れておくほうがよいでしょう。3秒は永遠です。これが、トレーダーがめちゃくちゃ良いコンピューターに投資する理由です。前の答えを参照してください。Crypt.RNGは、新しい番号ごとにわずか0,0028ミリ秒かかります。0.0000028秒なので、処理にかかる時間と速度の重要性の点で9桁ずれています。
ヘンリック


4

誰もが暗号的に安全な乱数を必要としているわけではなく、より高速な単純乱数の恩恵を受ける可能性があります。おそらくもっと重要なのは、System.Random番号のシーケンスを制御できることです。

再作成する可能性のある乱数を使用するシミュレーションでは、同じシードを使用してシミュレーションを再実行します。特定の障害のあるシナリオを再生成する場合にも、バグを追跡するのに便利です。プログラムをクラッシュさせたのとまったく同じ乱数のシーケンスでプログラムを実行します。


2

セキュリティが必要ない場合、つまり、暗号的に強力な値ではなく、比較的不確定な値が必要な場合、Randomのインターフェイスははるかに簡単です。


2

ニーズが異なれば、RNGも異なります。暗号通貨の場合、乱数をできるだけランダムにする必要があります。モンテカルロシミュレーションでは、スペースを均等に埋め、既知の状態からRNGを開始できるようにする必要があります。


1
System.Randomだけがどちらかを実行した場合..まあ。
user2864740 2016

2

Random は乱数ジェネレーターではなく、決定論的な疑似乱数シーケンスジェネレーターであり、歴史的な理由からその名前が付けられています。

使用する理由 System.Randomは、これらのプロパティ、つまり決定論的シーケンスが必要な場合です。これは、同じシードで初期化されたときに同じシーケンスの結果を生成することが保証されています。

インターフェイスを犠牲にすることなく「ランダム性」を改善したい場合は、System.Randomいくつかのメソッドのオーバーライドから継承できます。

なぜ決定論的なシーケンスが必要なのですか

真のランダム性ではなく決定論的なシーケンスを持つ理由の1つは、再現性があるためです。

たとえば、数値シミュレーションを実行している場合、(真の)乱数でシーケンスを初期化し、使用された番号記録できます。

次に、デバッグの目的などでまったく同じシミュレーションを繰り返したい場合は、代わりに記録された値でシーケンスを初期化することで実行できます。

なぜこの特定の、あまり良くないシーケンスが必要なのですか?

私が考えることができる唯一の理由は、このクラスを使用する既存のコードとの下位互換性のためです。

つまり、残りのコードを変更せずにシーケンスを改善したい場合は、先に進んでください。


1

私はゲームを書きました(iPhoneのクリスタルスライダー:ここマップ上に「ランダムな」一連の宝石(画像)を配置)を作成しました。マップを好きなように回転させて選択すると、それらは消えました。--Bejeweledに似ています。私はRandom()を使用していましたが、電話が起動してから100nsティックの数でシードされました。これはかなりランダムなシードです。

素晴らしいと思いました私は1〜3宝石を除いて2まったく同じになるだろう、それは2色の90かそこらの宝石-of互いにほぼ同一であったゲームを、生成することに!90枚のコインを裏返し、1〜3回の裏返しを除いて同じパターンを取得した場合、それはほとんどありません。同じことを示すスクリーンショットがいくつかあります。System.Random()がいかに悪かったかにショックを受けました!私は自分のコードにひどく間違った何かを書いたに違いないと思い、それを間違って使用していました。私は間違っていました、それは発電機でした。

実験として、そして最終的な解決策として、1985年頃から使用している乱数ジェネレーターに戻りました。これは非常に優れています。それはより速く、繰り返される前に1.3 * 10 ^ 154(2 ^ 521)の期間があります。元のアルゴリズムは16ビットの数値でシードされていましたが、それを32ビットの数値に変更し、初期のシードを改善しました。

オリジナルのものはここにあります:

ftp://ftp.grnet.gr/pub/lang/algorithms/c/jpl-c/random.c

何年にもわたって、私はこれで考えられるすべての乱数テストを投げてきました、そしてそれはそれらのすべてを通り過ぎました。暗号化されたものとして価値があるとは思いませんが、「return * p ++;」と同じ速さで数値を返します。521ビットがなくなるまで、ビットに対してクイックプロセスを実行して、新しいランダムなビットを作成します。

私はC#ラッパーを作成しました-それをJPLRandom()と呼び、Random()と同じインターフェイスを実装し、コード内でそれを呼び出したすべての場所を変更しました。

違いは非常に良かった-OMG私は驚いた-パターンで90ほどの宝石の画面を見ただけではわからないはずですが、これに続いてゲームの緊急リリースを行いました。

そして、System.Random()を二度と使用することはありません。彼らのバージョンが今30年前のものに吹き飛ばされていることに私はショックを受けました!

-Traderhutゲーム


3
私の最初の推測は、あなたがRandomあまりにも頻繁に再作成したということです。Nextそのインスタンスを何度も呼び出すと、一度だけ作成する必要があります。Random悪くはないが、それは悪いです。この問題を示すシードのペアと一緒にサンプルプログラムを投稿できますか?
CodesInChaos 2014年

コードはおおよそようだった次のコードは、各レベルの開始時にランダム()を作成します(それは、より後のものよりレベル1との大きな問題だった):
Traderhutゲーム

Rnd = new Random((uint)GameSeed); NextGameSeed = Rnd.Next(2000000000); 各レベルは、新しいシードで作成された新しいランダムを使用しました-シードはレベルごとに保存されたため、マップを再作成し、ランダムシードのシーケンスが一致したことを確認できます。これにより、ゲームが解決された有効な一連のマップであることを確認し、ゲームを再作成できます。
Traderhut Games 2014

そして最初に、RandomはSystem.DateTime.Now.Ticks(または0)に基づいて作成され、次に上記のRnd.Next()と同じ呼び出しを使用してGameSeedが選択されました。これができない場合は、乱数ジェネレーターのシードに重大な問題があります。
Traderhut Games 2014

これは元の質問に対する答えではありません!
Mike Dinescu 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.