java.util.Randomとjava.security.SecureRandomの違い


202

私のチームはランダムトークンを生成するサーバー側のコード(Javaで)を渡されましたが、それについて質問があります-

これらのトークンの目的はかなり機密性が高く、セッションID、パスワードリセットリンクなどに使用されます。したがって、誰かがトークンを推測したり、ブルートフォースで実行したりしないように、暗号的にランダムにする必要があります。トークンは「長い」ため、64ビット長です。

コードは現在、java.util.Randomクラスを使用してこれらのトークンを生成しています。ドキュメントのためのjava.util.Random明確では次のように述べています:

java.util.Randomのインスタンスは暗号的に安全ではありません。代わりにSecureRandomを使用して、セキュリティ上重要なアプリケーションで使用するための暗号的に安全な疑似乱数ジェネレータを取得することを検討してください。

ただし、コードが現在使用している方法java.util.Randomはこれです。クラスをインスタンス化java.security.SecureRandomし、SecureRandom.nextLong()メソッドを使用して、java.util.Randomクラスのインスタンス化に使用されるシードを取得します。次に、java.util.Random.nextLong()メソッドを使用してトークンを生成します。

だから今私の質問-がjava.util.Random使用してシードされていることを考えると、それはまだ安全java.security.SecureRandomですか?java.security.SecureRandomトークンの生成にのみ使用するようにコードを変更する必要がありますか?

現在、コードシードRandomは起動時に1回です


14
シードされると、java.util.Randomからの出力は数値の確定的なシーケンスになります。あなたはそれを望まないかもしれません。
PeterŠtibraný12年

1
コードRandomは起動時に一度シードするのですか、それともトークンごとに新しいシードをシードするのですか?うまくいけば、これは愚かな質問ですが、私はチェックしたいと思いました。
トムアンダーソン

8
ランダムは48ビットの内部状態のみを持ち、nextLong()への2 ^ 48呼び出し後に繰り返されます。longつまり、すべての可能なdouble値または値が生成されるわけではありません。
Peter Lawrey、

3
別の深刻な問題があります。64ビットは1.84 * 10 ^ 19の可能な組み合わせを意味し、高度な攻撃に耐えるには少なすぎます。60時間で毎秒90 * 10 ^ 9鍵で56ビットDESコード(係数256未満)をクラックしたマシンが世の中にあります。128ビットまたは2つのlongを使用してください!
Thorsten S. 2012

回答:


232

標準のOracle JDK 7実装では、線形合同ジェネレーターと呼ばれるものを使用して、でランダムな値を生成しjava.util.Randomます。

採取されたjava.util.Random方法にコメントから、ソースコード(JDKの7u2)protected int next(int bits)ランダム値を生成するものです:

これは、DHレーマーによって定義され、コンピュータプログラミングの芸術、第3巻: セミノミカルアルゴリズム、セクション3.2.1のドナルドE.クヌースによって説明されている線形合同擬似乱数ジェネレータ です。

線形合同ジェネレーターの予測可能性

Hugo Krawczykは、これらのLCGをどのように予測できるかについてかなり良い論文を書きました(「合同ジェネレーターを予測する方法」)。幸運で興味があれば、無料でダウンロード可能なバージョンをWebで見つけることができます。また、セキュリティが重要な目的でLCGを使用してはならないことを明確に示す、さらに多くの調査があります。これはまた、現在の乱数予測可能であること、つまりセッションIDなどには望ましくないことを意味します。

線形合同ジェネレーターを壊す方法

攻撃者は、完全なサイクルが間違っているとLCGが繰り返されるのを待たなければならないという想定。最適なサイクル(再帰関係の係数m)を使用しても、完全なサイクルよりもはるかに短い時間で将来の値を予測することは非常に簡単です。結局のところ、それは解く必要があるモジュラー方程式の集まりにすぎず、LCGの十分な出力値を観察するとすぐに簡単になります。

セキュリティは、「より良い」シードでは改善されません。ランダムに生成された値をシードSecureRandomするか、サイコロを数回振って値を生成するかは問題ではありません。

攻撃者は、観察された出力値からシードを単純に計算します。これは、の場合、2 ^ 48より大幅に時間がかかりませんjava.util.Random。信じられない人は、この実験を試してみることができます。この実験ではRandom2 ^(16)の時間で2つ(!)の出力値のみを観察して将来の出力を予測できることが示されています。最近のコンピューターでは、今すぐ乱数の出力を予測するのに1秒もかかりません。

結論

現在のコードを置き換えます。SecureRandom排他的に使用します。そうすれば、少なくとも結果を予測するのが困難になるという少しの保証があります。暗号的に安全なPRNGのプロパティが必要な場合(あなたの場合、それがあなたの望んでいることです)、次に行くSecureRandomだけです。それが使用されることになっていた方法を変更することについて賢明であることは、ほとんど常に安全性の低い何かをもたらすでしょう...


4
非常に有用、あなたも...(あなたがランダムがどのように動作するかを説明同じように)のSecureRandomがどのように動作するかを説明できるかもしれ
gresdiplitude

4
これは、
secureRandom

私は知っている、その教訓を難しい方法で学んだ。しかし、タフなサイファーと見つけにくいソースはうまく機能します。Notchはそれについて何かを学ぶことができました(ユーザーのパスワードを.lastloginファイルにエンコードし、「passwordfile」をキーとして使用する基本的な暗号化でエンコードしました)
Azulflame

1
ここでの本当の質問:Javaが同様のAPIでより安全なprngを生成できる場合、なぜ壊れたものを単に置き換えなかったのですか?
Joel Coehoorn、2012年

11
@JoelCoehoorn Random壊れているわけではありません。さまざまなシナリオで使用する必要があります。もちろん、常にSecureRandomを使用できます。しかし、一般的にSecureRandomは、純粋よりも著しく遅いですRandom。また、優れた統計的特性と優れたパフォーマンスのみに関心がある場合もありますが、セキュリティについてはあまり気にしません。モンテカルロシミュレーションが良い例です。私は同様の答えそれについてコメントしました、多分あなたはそれが役に立つでしょう。
エンボスは

72

ランダムには48ビットしかありませんが、SecureRandomには最大128ビットを含めることができます。したがって、安全なランダムで繰り返される可能性は非常に小さいです。

ランダムはをsystem clockシードとして使用するか、シードを生成するために使用します。したがって、攻撃者がシードが生成された時刻を知っていれば、簡単に再現できます。しかし、SecureRandomRandom Dataあなたからos取得し(キーストロークの間隔などである場合があります-ほとんどのOSはこれらのデータを収集してファイルに保存します /dev/random and /dev/urandom in case of linux/solaris)、それをシードとして使用します。
したがって、小さいトークンサイズが問題ない場合(ランダムの場合)、SecureRandomを使用してシードを生成しているため、コードを変更せずに引き続き使用できます。しかし、より大きなトークンが必要な場合(これを適用できないbrute force attacks)、SecureRandomを使用します-
ランダムな公正な2^48試みが必要な場合、今日の高度なCPUでは、実用的な時間でそれを壊すことが可能です。しかし、安全なランダムな2^128試みが必要となるため、今日の高度なマシンを使用した場合でも、何年もかかります。

詳細については、このリンクを参照してください。
編集
@embossによって提供されたリンクを読んだ後、シードは、たとえランダムであっても、java.util.Randomで使用してはならないことは明らかです。出力を観察することでシードを計算するのは非常に簡単です。

SecureRandomを使用する- への呼び出しごとにファイルからランダムな値を取得するため、上記のリンクにあるように、ネイティブPRNGを使用し/dev/randomます。nextBytes()。彼はの内容を制御されていない限り、出力を観察し、攻撃者ができなくなります。この方法では、何を作るために/dev/randomファイルを(これは非常に低いです)SHA1 PRNGアルゴリズム計算に一度だけして、VMの場合、シードはそれを用いヶ月間実行されていますシードは、出力を受動的に監視している攻撃者によってクラックされる可能性があります。 -OSがランダムなバイト(エントロピー)をに書き込めるよりも速く呼び出す場合、NATIVE PRNGを使用すると問題が発生する可能性があります 。その場合は、SecureRandomのSHA1 PRNGインスタンスを使用し、数分(または一定の間隔)ごとに、このインスタンスにfromの値をシードします。


nextBytes()/dev/randomnextBytes()SecureRandomのNATIVE PRNGインスタンスの例。これらの2つの並列処理を実行することにより、オペレーティングシステムによって取得されたエントロピーを使い果たすことなく、真のランダム値で定期的にシードすることが保証されます。


を予測するのに必要なのは2 ^ 48よりはるかに少なくRandom、OPはまったく使用すべきではありませんRandom
エンボス

@emboss:私はブルートフォースについて話している。
アシュウィン、

1
Linuxに注意してください:エントロピーが枯渇する可能性があります(ハードウェアよりもVMで多くなります)。見て/proc/sys/kernel/random/entropy_availいくつかのスレッドを持つとチェックが上の読み取り時にあまりにも長い間待って何があることをダンプしない/dev/random
イヴ・マーティン

2
Oracle JRE(少なくとも1.7)はデフォルトで/ dev / urandomで機能し、/ dev / randomでは機能しないため、回答のサフィックスが正しくないことに注意してください。$ JAVA_HOME / lib / security / java.securityでsecurerandom.sourceプロパティを確認します
Boaz

1
java.securityファイルには、file:/// dev / urandomではなく、securerandom.source = file:/ dev / urandom(ファイルプロトコルのコロンの後に2つのスラッシュ、次にファイルシステムのルートのスラッシュが1つ)があり、フォールバックしました。 / dev / randomへ。これにより、エントロピープールの枯渇に関する問題が発生しました。編集できなかったため、アプリの起動時にシステムプロパティjava.security.egdを適切な値に設定する必要がありました。
maxpolk 2015年

11

java.util.Random.nextLong()同じシードで2回実行すると、同じ数が生成されます。セキュリティ上の理由からjava.security.SecureRandom、予測がはるかに難しいため、使い続ける必要があります。

2つのクラスは似ています。リファクタリングツールで変更Randomする必要があるだけSecureRandomで、既存のコードのほとんどが機能します。


11
PRNGの2つのインスタンスを取得して同じ値をシードする場合、SecureRandomを使用してもそれは変わりません。すべてのPRNGは確定的であるため、シードを知っていれば予測可能です。
Robert

1
SecureRandomにはさまざまな実装があり、PRNGとそうでないものがあります。一方、java.util.Randomは常にPRNGです(Javadocで定義されています)。
PeterŠtibraný

3

既存のコードの変更が手頃なタスクである場合は、Javadocで提案されているSecureRandomクラスを使用することをお勧めします。

Randomクラスの実装でSecureRandomクラスを内部的に使用している場合でも、当然のことと考えてはなりません。

  1. 他のVM実装でも同じことが行われます。
  2. JDKの将来のバージョンでのRandomクラスの実装は、引き続きSecureRandomクラスを使用します

したがって、ドキュメントの提案に従い、SecureRandomを直接使用することをお勧めします。


元の質問ではjava.util.Random実装がSecureRandom内部で使用されているとは思われませんが、コードSecureRandomをシードするために使用すると述べていますRandom。それでも、これまでのところ両方の答えに同意します。SecureRandom明確に決定的なソリューションを回避するために使用するのが最善です。
Palpatim

2

の現在の参照実装は、現在のシードの32ビットを直接公開するjava.util.Random.nextLong()メソッドnext(int)を2回呼び出します。

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

結果の上位32ビットは、そのnextLong()時点でのシードのビットです。シードの幅は48ビット(javadocによると)であるため、残りの16ビット(65.536回しか試行しない)を反復して、2番目の32ビットを生成したシードを特定するだけで十分です。

シードがわかったら、後続のすべてのトークンを簡単に計算できます。

nextLong()直接の出力を使用して、PNGの秘密の一部を、わずかな労力で完全な秘密を計算できる程度まで。危険な!

* 2番目の32ビットが負の場合、多少の労力が必要ですが、それを見つけることができます。


正しい。jazzy.id.au/default/2010/09/20/…で java.util.randomをすばやくクラックする方法を確認してください
ingyhere 14年

2

種は無意味です。優れた乱数発生器は、選択した素数が異なります。すべてのランダムジェネレーターは数値から始まり、「リング」を反復します。つまり、古い内部値を使用して、ある数値から次の数値に移動します。しかし、しばらくすると、最初からやり直し、最初からやり直します。つまり、サイクルを実行します。(ランダムジェネレーターからの戻り値は内部値ではありません)

リングを作成するために素数を使用する場合、すべての可能な数の完全なサイクルを完了する前に、そのリングのすべての数が選択されます。素数でない場合、すべての数が選択されるわけではなく、サイクルが短くなります。

素数が大きいほど、サイクルが長くなり、最初の要素に再び戻る前になります。したがって、安全なランダムジェネレーターは、最初に戻る前に、より長いサイクルを持っているだけなので、より安全です。短いサイクルほど簡単に数の生成を予測することはできません。

つまり、すべてを置き換える必要があります。


0

RandomとsecureRandomの違い、およびSecureRandomクラスの重要性を簡単に理解できるように、非常に基本的な単語を使用してみます。

OTP(ワンタイムパスワード)がどのように生成されるのか疑問に思ったことはありませんか?OTPを生成するには、RandomクラスとSecureRandomクラスも使用します。OTPを強力にするために、SecureRandomは2 ^ 128回かかったのでより優れています。現在のマシンではほぼ不可能であるOTPをクラックしますが、ランダムクラスを使用すると、OTPは、たった2 ^ 48回試してみてください。

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