私のチームは、ランダムなトークンを生成する(Javaの)サーバー側コードを引き渡しましたが、同じことに関して質問があります-
これらのトークンの目的はかなり敏感です-セッションID、パスワードリセットリンクなどに使用されます。だから誰かが推測したり、無理矢理実行できるように、暗号的にランダムにする必要があります。トークンは「長い」ため、64ビット長です。
現在、コードはJava.util.Random
クラスを使用してこれらのトークンを生成しています。 Java.util.Random
のドキュメント([ http://docs.Oracle.com/javase/7/docs/api/Java/util/Random.html] [1] )には次のように明記されています。
Java.util.Randomのインスタンスは暗号的に安全ではありません。代わりに、SecureRandomを使用して、セキュリティが重要なアプリケーションで使用する暗号的に安全な擬似乱数ジェネレーターを取得することを検討してください。
ただし、コードが現在Java.util.Random
を使用している方法は次のとおりです。Java.security.SecureRandom
クラスをインスタンス化し、SecureRandom.nextLong()
メソッドを使用して、Java.util.Random
classのインスタンス化に使用されるシードを取得します。次に、Java.util.Random.nextLong()
メソッドを使用してトークンを生成します。
だから今私の質問-Java.util.Random
がJava.security.SecureRandom
を使用してシードされていることを考えると、まだ安全ではありませんか? Java.security.SecureRandom
を排他的に使用してトークンを生成するようにコードを変更する必要がありますか?
現在、コードシードは起動時にRandom
を1回使用します
標準のOracle JDK 7実装では、線形合同ジェネレーターと呼ばれるものを使用して、Java.util.Random
にランダムな値を生成します。
Java.util.Random
ソースコード(JDK 7u2)、protected int next(int bits)
メソッドに関するコメントから取得します。これは、ランダムな値を生成するメソッドです。
これは、D。H. Lehmerによって定義され、Donald E. Knuthによって次のように説明されている線形合同擬似乱数ジェネレータです。 コンピュータプログラミングの芸術、 ボリューム3: 半数値アルゴリズム、セクション3.2.1。
Hugo Krawczykは、これらのLCGをどのように予測できるかについて、かなり良い論文を書きました(「合同ジェネレーターを予測する方法」)。あなたが幸運で興味があるなら、あなたはまだウェブ上でそれの無料でダウンロード可能なバージョンを見つけるかもしれません。また、セキュリティクリティカルな目的でLCGを使用するneverを使用する必要があることを明確に示す多くの研究があります。これはまた、あなたの乱数areが現在予測可能であることを意味します。これはセッションIDなどには必要ありません。
攻撃者は、フルサイクル後にLCGが繰り返されるのを待たなければならないという仮定は誤りです。最適なサイクル(再帰関係のモジュラスm)であっても、フルサイクルよりもはるかに短い時間で将来の値を予測することは非常に簡単です。結局のところ、解く必要があるのはモジュラー方程式の束にすぎず、LCGの十分な出力値を観察するとすぐに簡単になります。
セキュリティは、「より良い」シードでは改善されません。 SecureRandom
によって生成されたランダムな値をシードするか、サイコロを数回振って値を生成するかどうかは、単に問題ではありません。
攻撃者は、観測された出力値からシードを単純に計算します。 Java.util.Random
の場合、これは大幅に少ない 2 ^ 48よりも時間がかかります。不信者はこれを試してみることができます 実験 。ここでは、およそ2 ^ 16の時間内に2つの(!)出力値のみを観測する将来のRandom
出力を予測できることが示されています。現在の乱数の出力を予測するのに、最新のコンピューターでは1秒もかかりません。
現在のコードを置き換えます。 SecureRandom
のみを使用してください。そうすれば、少なくとも結果を予測するのが難しいという保証が少しあります。暗号的に安全なPRNGのプロパティが必要な場合(あなたの場合、それはあなたが望むものです)、SecureRandom
だけで行かなければなりません。それが使用されるはずであった方法を変えることについて賢いことは、ほとんど常により安全でない何かをもたらすでしょう...
ランダムには48ビットしかありませんが、SecureRandomは128ビットまで持つことができます。したがって、securerandomで繰り返す可能性は非常に小さいです。
Randomは、system clock
をシードとして使用して、シードを生成します。そのため、攻撃者がシードが生成された時間を知っていれば、簡単に再現できます。ただし、SecureRandomはos
からRandom Data
を取得します(キーストロークの間隔などです-ほとんどのOSはこれらのデータを収集してファイルに保存します-/dev/random and /dev/urandom in case of linux/solaris
)。これをシードとして使用します。
したがって、小さいトークンサイズで問題ない場合(ランダムの場合)、SecureRandomを使用してシードを生成しているため、コードを変更せずに引き続き使用できます。ただし、より大きなトークン(brute force attacks
の対象にできない)が必要な場合は、SecureRandomを使用します-
ランダムな場合は、2^48
の試行のみが必要です。今日の高度なCPUでは、実用的な時間でそれを破ることが可能です。しかし、セキュアランダムの場合、2^128
の試行が必要になります。これは、今日の高度なマシンであっても何年も壊れるでしょう。
詳細については、 this リンクを参照してください。
編集
@ embossが提供するリンクを読んだ後、シードは、たとえランダムであっても、Java.util.Randomで使用すべきではないことは明らかです。出力を観察することでシードを計算するのは非常に簡単です。
SecureRandomに移動-Native PRNGを使用します(上記のリンクに記載)。 nextBytes()
の呼び出しごとに/dev/random
ファイル。このように、出力を監視している攻撃者は、/dev/random
ファイルの内容を制御していない限り、何も確認することができません(これはほとんどありません)
sha1 prngアルゴリズムは一度だけシードを計算し、同じシードを使用してVMが数か月間実行されている場合、攻撃者によってクラックされる可能性があります受動的に出力を観察している人。
NOTE-OSが/dev/random
にランダムバイト(エントロピー)を書き込めるよりも速くnextBytes()
を呼び出している場合、問題が発生する可能性がありますNATIVE PRNGを使用します。その場合、SecureRandomのSHA1 PRNGインスタンスを使用し、数分(または間隔)ごとに、このインスタンスにSecureRandomのNATIVE PRNGインスタンスのnextBytes()
の値をシードします。これらの2つの並列処理を実行すると、オペレーティングシステムによって取得されたエントロピーを使い果たしずに、真のランダム値を定期的にシードすることが保証されます。
同じシードでJava.util.Random.nextLong()
を2回実行すると、同じ数が生成されます。セキュリティ上の理由から、Java.security.SecureRandom
を使い続けたいのは、予測がはるかに難しいからです。
2つのクラスは似ています。リファクタリングツールでRandom
をSecureRandom
に変更するだけで、既存のコードのほとんどが機能します。
既存のコードの変更が手頃な価格のタスクである場合、Javadocで提案されているSecureRandomクラスを使用することをお勧めします。
Randomクラスの実装がSecureRandomクラスを内部で使用している場合でも。あなたはそれを当然のことと考えてはいけません:
したがって、ドキュメントの提案に従い、SecureRandomを直接使用することをお勧めします。
種は無意味です。適切なランダムジェネレーターは、選択した素数が異なります。すべてのランダムジェネレーターは数値から始まり、「リング」を反復処理します。つまり、古い内部値を使用して、ある番号から次の番号に移動します。しかし、しばらくすると、再び最初に到達し、最初からやり直します。したがって、サイクルを実行します。 (ランダムジェネレーターからの戻り値は内部値ではありません)
リングを作成するために素数を使用する場合、すべての可能な番号の完全なサイクルを完了する前に、そのリング内のすべての番号が選択されます。非素数を取る場合、すべての数が選択されるわけではなく、サイクルが短くなります。
素数が大きいほど、最初の要素に再び戻るまでのサイクルが長くなります。したがって、安全なランダムジェネレーターは、最初に到達するまでのサイクルが長いだけであるため、安全です。サイクルの短縮ほど簡単に番号生成を予測することはできません。
言い換えると、すべてを置き換える必要があります。
Java.util.Random.nextLong()
の現在の参照実装は、directly現在のシードの32ビットを公開するメソッド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);
}
nextLong()
の結果の上位32ビットは、その時点でのシードのビットです。シードの幅は48ビットなので(javadocによると)、残りの16ビット(たった65.536回の試行)を反復処理して、2番目の32ビットを生成したシードを決定するだけで十分です。
シードがわかれば、後続のすべてのトークンを簡単に計算できます。
nextLong()
の出力を直接使用して、PNGの秘密の一部を、ほんの少しの手間で秘密全体を計算できる程度まで。危険!
* 2番目の32ビットが負の場合、多少の努力が必要ですが、それを見つけることができます。
RandomとsecureRandomの違いとSecureRandomクラスの重要性を簡単に理解できるように、非常に基本的な言葉を使用するようにします。
OTP(ワンタイムパスワード)がどのように生成されるのだろうか? OTPを生成するには、RandomおよびSecureRandomクラスも使用します。 OTPを強力にするには、現在のマシンではほとんど不可能なOTPをクラックするのに2 ^ 128の試行が必要だったのでSecureRandomが優れていますが、ランダムクラスを使用すると、データに害を及ぼす可能性のある誰かによってOTPがクラックされる可能性がありますちょうど2 ^ 48を試して、クラックします。