web-dev-qa-db-ja.com

Math.random()が暗号的に安全であるように設計されていないのはなぜですか?

JavaScript Math.random() 関数は、単一のIEEE浮動小数点値nを返すように設計されており、0≤n <1.出力がnot暗号学的に安全であることは広く知られています(または少なくとも知られているはずです)。最近のほとんどの実装では XorShift128 + アルゴリズムを使用していますが、これは 簡単に壊れる です。より良いランダム性が必要なときに人々が 誤って使用することは珍しくありません 、なぜブラウザはそれをCSPRNGに置き換えないのですか? Operaが少なくとも *を実行することを知っています。 XorShift128 +がCSPRNGよりも高速であると考えることができる唯一の理由は、ChaCha8またはAES-CTRを使用して1秒あたり数百メガバイトを出力することです。これらは、十分に最適化された実装がシステムのメモリ速度によってのみボトルネックになる可能性があるほど高速であることがよくあります。 ChaCha20の最適化されていない実装でさえ、すべてのアーキテクチャで非常に高速であり、ChaCha8は2倍以上高速です。

規格が暗号の使用に対する適合性を明示的に保証しないため、それをCSPRNGとして再定義できなかったことを理解していますが、ブラウザベンダーが自主的に行うことには欠点がないようです。これは、標準に違反することなく、多数のWebアプリケーションのバグの影響を軽減します(出力が最も近い偶数に丸められる必要があります IEEE 754 数値) 、パフォーマンスの低下、またはWebアプリケーションとの互換性の破壊。


編集:数人が、標準が暗号セキュリティのためにそれに頼ることができないと言っていても、これが潜在的に人々がこの機能を悪用する可能性があると指摘しました。私の考えでは、CSPRNGを使用することがネットセキュリティ上の利点になるかどうかを決定する2つの相反する要素があります。

  1. 誤った安心感- window.cryptoなど、この目的のために設計された関数を使用しない人の数 、意図したターゲットプラットフォームで暗号的に安全であるため、代わりにMath.random()を使用することを決定します。

  2. Opportunistic security-何も知らず、保護されている機密性の高いアプリケーションでとにかくMath.random()を使用する人の数彼ら自身の間違い。明らかに、代わりにそれらを教育する方が良いでしょうが、これは常に可能であるとは限りません。

自分の過ちから守られる人の数は、誤った安心感に陥る人の数を大幅に上回っていると考えるのは安全だと思われます。

* CodesInChaosが指摘するように、OperaはChromiumに基づいているため、これはもはや真実ではありません。


いくつかの主要なブラウザには、この機能を暗号的に安全な代替手段で置き換えることを提案するバグレポートがありますが、提案された安全な変更はありませんでした。

引数for変更は基本的に私のものと一致します。これに対する議論は、マイクロベンチマークでのパフォーマンスの低下(現実の世界ではほとんど影響がない)から、誤解や神話など、ランダム性が高まるにつれてCSPRNGが時間とともに弱まるという誤った考えなど、さまざまです。最終的に、Chromiumはまったく新しい暗号オブジェクトを作成し、FirefoxはそれらのRNGをXorShift128 +アルゴリズムに置き換えました。 Math.random()関数は完全に予測可能です。

228
forest

私はJScriptの実装者の1人であり、1990年代半ばから後半にかけてECMA委員会のメンバーだったので、ここでいくつかの歴史的展望を提供できます。

JavaScript Math.random()関数は、0と1の間の浮動小数点値を返すように設計されています。出力が暗号的に安全でないことは広く知られています(または少なくともそうである必要があります)

まず、多くのRNG APIの設計はhorribleです。同じ数の長いシーケンスを生成するために、.NET Randomクラスが複数の方法で簡単に誤用される可能性があるという事実は恐ろしいです。それを使用する自然な方法が間違った方法でもあるAPIは、「失敗のピット」APIです。私たちのAPIは、自然な方法と正しい方法が同じである成功のピットになりたいです。

私たちが今知っていることを知っていれば、JSランダムAPIは異なるものになると言って差し支えないと思います。名前を「疑似ランダム」に変更するなどの単純なものでも役立ちます。これは、ご存知のように、場合によっては実装の詳細が重要になるためです。アーキテクチャレベルでは、random()を、単に数値を返すのではなく、ランダムまたは疑似ランダムシーケンスを表すオブジェクトを返すファクトリにしたいのには、十分な理由があります。等々。学んだ教訓。

次に、JSの基本的な設計目的が1990年代に何であったかを思い出してみましょう。 マウスを動かすとサルが踊ります。私たちはインライン式スクリプトを通常と考え、2〜10行のスクリプトブロックを一般的なものと考えました。また、誰かがページに100行のスクリプトを書く可能性があるという考えは、非常に珍しいものでした。 1万行のJSプログラムを初めて見たとき、C++バージョンに比べて遅いために助けを求めてきた人々に最初に尋ねた質問は、「あなたは非常識ですか?!10KLOC JS ?! 」

誰もが暗号のランダム性を必要とするという概念JS内も同様に狂気でした。猿の動きが暗号強度を予測できないものにする必要がありますか?ありそうもない。

また、それが1990年代半ばだったことを思い出してください。そこにいなかったら、それは今日とは非常に異なる世界暗号に関する限りだったと言えます 暗号のエクスポート を参照してください。

MS-Legalチームから膨大な法的助言を得ることなく、ブラウザに同梱されているものに暗号強度のランダム性を組み込むことさえできませんconsidered私は、出荷コードが考慮されている世界で10フィートのポールで暗号に触れたくありませんでした国家の敵に弾薬をエクスポートする。これは、今日の観点からは奇妙に聞こえますが、 それがあった世界 です。

なぜブラウザはそれをCSPRNGに置き換えないのですか?

ブラウザの作成者は、変更を行わない理由を提供する必要はありません。変更は費用がかかり、より良い変更から努力を奪います。すべての変更には莫大な機会費用があります。

むしろ、なぜ変更を行うことが良いアイデアであるかだけでなく、なぜそれが彼らの時間の可能な限りの最善の使用であるかという議論を提供しなければなりません。これは小額の払い戻しです。

標準では暗号化の使用に対する適合性が明示的に保証されていないため、CSPRNGとして再定義できないことを理解していますが、とにかくそれを行うことの欠点はないようです

欠点は、開発者がstillであり、ランダム性が暗号強度であるかどうかを確実に知ることができず、保証されていないプロパティに依存するという罠にさらに簡単に陥る可能性があることです。標準。提案された変更は実際には問題を解決しません。これは設計上の問題です。

443
Eric Lippert

実際にはMath.random()の代わりに暗号化された安全な方法があるためです:

window.crypto.getRandomValues(typedArray)

これにより、開発者はジョブに適したツールを使用できます。ゲームのきれいな写真や略奪品を生成したい場合は、高速Math.random()を使用してください。暗号で保護された乱数が必要な場合は、より高価なwindow.crypto

116
Philipp

JavaScript(JS)は1995年に発明されました。

  1. 違法である可能性があります:暗号化は1995年に依然として厳格な輸出管理下にあったため、優れたCSPRNGはブラウザで配布することさえ合法ではなかったかもしれません。
  2. パフォーマンス:歴史的に、CSPRNG(暗号的に安全な疑似乱数ジェネレータ)はPRNGよりもはるかに遅いので、なぜデフォルトでCSPRNGを使用するのですか?
  3. セキュリティの考え方はありません:1995年にSSLが発明されました。事実上、それをサポートするサーバーはまだなく、インターネットは電話回線で構成され、パブリックフォーラム( BBSes )および MUDs で使用されていました。暗号化は諜報機関にとって何かでした。
  4. 不要:JavaScriptが発明されたばかりなので、Webアプリケーションはまだ存在していませんでした。これは、ページをより動的にするために解釈された(したがって遅い)言語として設計されました。ランダムな関数にデフォルトで遅い(そしてかろうじて存在しない)CSPRNGを使用することは誰の心にも及ばなかっただろう。
  5. 実際にはほとんど必要がなく、代替手段はありませんでした:JavaScriptは、2013年12月まで、CSPRNGに対して一般的にサポートされているAPIも持っていなかったため、適切な暗号化Webアプリケーションでは、数年前まではほとんど不可能でした。
  6. 整合性:既存の関数を別の意味に変更するのではなく、別の名前で新しい関数を作成しました。これで、_crypto.getRandomValues_を介してCSPRNGにアクセスできます。

要約すると、レガシーですが、速度と一貫性もあります。すべてのハードウェアがAESをサポートしていると想定することも、RDRANDの可用性やセキュリティに依存することもできないため、安全でないPRNGは依然としてはるかに高速です。

個人的には、すべてのランダム関数をCSPRNGと入れ替えて、高速で安全でない関数の名前をfast_insecure_random()のように変更するときがきたと思います。それらは、多くの乱数を必要とするシミュレーションを行う科学者または他の人々だけが必要とするべきですが、RNGの予測可能性は問題ではありません。しかし、20年の歴史を持つ機能の場合、代替手段が現在(たったの4年)(2018年に)しか存在しなかったので、なぜ今はまだその時点ではないのか理解できます。

69
Luc

コメントには長すぎます。

私はあなたの質問に欠陥のある前提があると信じています:

現代の(そしてそれほど現代ではない)コンピューターでは、ChaCha8またはAES-CTRを使用して毎秒数百メガバイトを出力するのは簡単です

AC接続されたマシンのデスクトップブラウザー、または大きなhonkin 10Ahバッテリーを搭載したラップトップを考えています。

私たちはますますモバイル第一の世界に住んでおり、最近のモバイルデバイスは非常に強力ですが、熱とバッテリー寿命という2つの重要な制約があります。 100Cに到達しやすいデスクトッププロセッサとは異なり、スマートフォンユーザーの手を焼くことはできません。そして、携帯電話のバッテリーは通常、ラップトップ(運が良ければ)のたぶん 1/3しか保持しません。必要がなければ、発熱量/消費電力を増やす理由はありません。

20
Jared Smith

より大きな理由は、Math.random()に代わるものがあるためです:Philippの回答を参照してください。したがって、強力な暗号化を必要とする人なら誰でもそれを利用でき、そうでない人は時間と(バッテリー)電力を節約できます。

しかし、あなたが「より強力な代替案があったとしても、開発者はMath.random()をまったく同じに更新しなかったのはなぜですか?つまり、random()をgetRandomValues()の派生物にしました-自動的に強化するためにたくさんのアプリがありますか?」 -そして、私はこれを実際に信頼できるとは思わないが、決定を下した開発者以外はそうではない(更新:そしてFateがそうするであろうように 私たちはそのような答えを持っている )。

原則として-あなたがすでに言ったように-強い理由はありません

さらに、ほとんどの開発チームには、実行が急務である重要なバックログがあります。そして、このような明らかに小さな変更でも、テスト、回帰、および黄金の「に違反しない場合は修正しないでください」ルールが必要です。 YAGNI基準。

16
LSerni

乱数と暗号乱数ビットは完全に異なる動物です。同じ目的で使用されることすらありません。 0から42の間で均等に分布する乱数が必要な場合は、明らかなパターンのない均等な分布が必要です。大きい数値を小さい数値で変更すると、それは正確に分布ではないことに注意してください。この例は、0から31までの乱数を27にした場合に簡単にわかります。0から4は、5から31の2倍の頻度で表示されます。

暗号ランダムについて話すまで、エントロピーの概念は議論さえされていません。少しのエントロピーにより、サーチスペースが2倍になり、数値を推測します(数値の対象ユーザー向け)。

暗号ランダムビットを要求すると、Nビットのエントロピーが要求されます。それを生成する関数が発見された場合(どのように複雑であっても)、この関数を知っている人の観点からは、実際には0ビットのエントロピーが存在するため、非自明なパターンを作成するだけでは十分ではありません。

これの良い例は、Fortunaのような疑似乱数ジェネレータです。最初の乱数(暗号ブロックが大きな数である場合)のキーで数値1を暗号化し、次に2番目の乱数のキーで数値2を暗号化します。暗号の(Kビットの)鍵を知らないユーザーに関しては、完全なNビット暗号ブロックは、そのブロックに対してNビットのエントロピーを持ちます。

それから100万ビットの疑似ランダムデータに拡張しても、同じキーKを使い続けると、Kビットのエントロピーしか得られません。つまり、100万ビットの本がある場合、 Kの下で単一の暗号で生成されたことがわかっている場合は、すべての暗号ストリームビットを推測しようとしないでください。鍵の推測に固執し、それから暗号ストリームを生成します。

そのため、乱数ジェネレーターは、達成できるので、より多くのランダム性で再シードされ続ける暗号であることがよくあります。比較すると、単純な[0,1]乱数ジェネレーターは、数値のビット数よりも多くのエントロピーを持つことはできません。また、通常は奇妙な分布になり、これも正確には望みどおりではありません。浮動小数点数が32ビットまたは64ビットのみである場合、暗号は数百ビットを必要とし、アルゴリズム自体がエントロピーの多くを取り除きます。ランダムビットで構成される浮動小数点表現。どんなディストリビューションになるかさえも知りません。

14
Rob

Youveは自分で質問に答えました。

規格は、暗号の使用に対する適合性を明示的に保証していません

したがって、実装を変更するのではなく、「仕事に適したツール」™を選択するように開発者を教育することに焦点を当てるべきです。

それと、一般的に使用される関数の実装を変更することによる技術的なオーバーヘッド、およびこの問題に対する特定の解決策がすでにあるという事実(@Philippsの回答を参照)を考えると、変更を行う説得力のある理由はありません。

8
richzilla

プログラミング言語の設計では、多くのことを考慮する必要があります。ブラウザーは非常に強力であり、現在、JavaScriptを大幅に最適化しています。しかし、組み込みシステムを検討する場合、ランダム性の良い情報源がない可能性があります。たとえば、nodeJS(のような)環境を実行するマイクロコントローラーがあります。

このようなマイクロコントローラーには、暗号で保護された乱数を保証するランダムソースがありません。したがって、乱数について強力な保証を行うプログラミング言語を実装できるようにするには、ピンにランダム入力を提供できるデバイスを接続する必要があります。また、十分なランダム性を提供するデバイスを構築し、デバイスからの入力を適切な方法で処理するには、かなりの知識が必要になります。

5
allo

他の人と同じように、Math.random()は通常は必要ないため、暗号的に安全ではないことを指摘します。しかし、私はさらに進んで、非常に正当な理由がない限り、暗号で保護されたアルゴリズムを仕様に書き込むのは賢明ではないと主張します。

暗号的に安全であるとはどういう意味ですか?さて、「まだ誰もそれを壊す方法を知らない」という退屈な定義は常にあります。しかし、誰かがそれを破るとどうなりますか? CSPRNGを指定した場合は、使用中のアルゴリズムを照会する方法も含める必要があります。そうしないと、エンドユーザーが何を取得しているかを確実に把握できるようになります。

これは、ユーザーが信頼できるジェネレーターを選択できるように、複数のジェネレーターをサポートできる必要性にもつながる可能性があります。これにより、非常に複雑になります。突然、APIの1行の関数がスイートになりました。

また、暗号について話し始めると、ジェネレーターでセキュリティを確保しようとすることについて話し始めます。 AESを使用して乱数を生成することについて言及していますが、AES実装はサイドチャネル攻撃の影響を受けないようにする必要がありますか?暗号化の保証を提供するという専用の目的でライブラリを作成している場合、その質問をする必要があるのはそれほど不合理ではありません。仕様については、それはひどく無理かもしれません。サイドチャネル攻撃に対する耐性はvery仕様の言語で表現するのが難しいことです。

そして、あなたはそれをスペックに入れることによって何を達成しましたか? PRNGのほとんどのユーザーは暗号化の保証をまったく必要としないため、CPUサイクルを無駄にしているだけです。暗号化の保証が必要な人は、そのような暗号に慣れるために必要な機能の完全なスイートをサポートするライブラリを探す可能性が高いため、とにかくMath.random()を信頼しません。残っているのは、あなたが言及している人口統計だけです。間違いを犯し、そうでないときにツールを使用した人々。ええと、私は経験から言うことができます、主要なプログラミング言語はnot誤って誤って使用できないAPIを探す場所です。彼らは、「あなたがこれをするなら、それはあなた自身の責任です」という言い回しでいっぱいです。

また、これを考慮してください:Math.random()を使用し、暗号化の保証を前提としている場合、アルゴリズムのどこかで致命的な暗号の誤りを犯す可能性はどれくらいありますか? CSPRNG Math.random()は、誤った安心感を与える可能性があり、さらに多くの間違いを見つける可能性があります。

5
Cort Ammon

誰もがここで少しニュアンスを見逃しているようです:暗号化アルゴリズムは、アルゴリズムのすべての実行にわたって数学的および統計的にランダムである数を必要とします。これは、たとえばゲームまたはアニメーションの実行中に、疑似乱数シーケンスを使用できることを意味します。これは、「ランダムな種類の」数字には完全に適しています。

ただし、この数値を操作または予測できる場合、たとえばシードされた乱数(これはウィンドウのランダム関数のデフォルトの動作です)の場合、このシードは実際に予測可能です。アプリケーションを操作して再起動し、シードされた乱数を使用できる場合は、選択する「乱数」の数を予測できます。これが可能な場合は、暗号化を無効にすることができます。二次的な懸念は、一部のアルゴリズムがスペクトル全体での数値の保証された分布を必要とすることかもしれません。これは、特定の擬似乱数発生器では保証できません。

暗号化乱数ジェネレーターには、エントロピーを作成するための大量の入力セットがあります。たとえば、マイク入力からのショットノイズ、時刻の目盛り、RAMレジスターのチェックサム、シリアル番号などを測定します。不可能ではないにせよ、できるだけ多くの入力を行います。その場合、操作と予測が非常に困難になります。暗号の意味では、パフォーマンスは目的ではなく、「真の」ランダム性です。

したがって、ユースケースによっては、乱数を適度にランダムで高性能に実装したい場合がありますが、diffie-hellman鍵交換を行う場合は、暗号化された安全なアルゴリズムが必要です。

3
Spence