Javaの擬似乱数生成器

乱数に関する基本概念

1. 乱数の性質を分類します。

  • ランダム性: この特性を満たす弱い擬似乱数を弱い擬似乱数と呼びます。この種の乱数は一般的なアプリケーションでのみ使用でき、Java の java.util.Random クラスなどの暗号化では使用できません。
  • 予測不可能性: この特性を満たす強力な擬似乱数。暗号化では、乱数にはランダム性に加えて予測不能性も必要です。つまり、履歴データから次の乱数を推測することは不可能です。
  • 非反復性: この特性を満たす真の乱数。反復不可能な乱数をソフトウェアだけで生成することはできません。コンピュータ ソフトウェアの内部状態は限られており、内部状態が同じである限り同じ数を生成する必要があり、これをサイクルともいいます。非再現性を満たすためには、温度や音などの物理現象を通じてのみ取得できますが、これにはハードウェアのサポートが必要であり、たとえば、Intel CPU には乱数発生器が用意されています。

上記のプロパティは下に行くほど厳密になり、包括的なプロパティを持ちます。たとえば、非再現性にはランダム性と予測不可能性が必要であり、予測不可能性にはランダム性が必要です。

説明: サイコロを繰り返し投げることによって生成される数列は反復不可能です。

2. 概要:

乱数はハードウェアまたはソフトウェアによって生成できます。センサーによって収集された熱や音など、ハードウェアによって生成される乱数は、真の乱数と呼ばれ、乱数ジェネレーター (RNG) とも呼ばれる、予測不可能で非反復的な自然現象です。

ソフトウェアによって生成される乱数を乱数(Pseudo Random Number Generator PRNG)といいます。ソフトウェアの状態は制限されているため、周期的である必要があります。乱数は「弱いランダム」と「強いランダム」に分けられます。

次に、Java での擬似乱数の使用を紹介します。

ランダムの概要

JDK7 より前は、java.util.Random が乱数生成ツール クラスとして広く使用されており、java.lang.Math での乱数生成も java.util.Random のインスタンスでした。Random クラスは、線形合同アルゴリズムを使用して乱数を生成します。このアルゴリズムは弱い擬似乱数アルゴリズムであり、予測不可能性がないため、暗号化には使用できません。

1. Random クラスはスレッドセーフです。

乱数の生成はシード Seed に関連しており、スレッドの安全性を確保するために、Random は CAS メカニズムを使用してスレッドの安全性を確保します。next() メソッドから、シードが継続的な CAS を通じて変更されていることがわかります。同時実行性が高いシナリオでは、CAS が失敗し、連続スピンが発生し、CPU の負荷が高すぎる可能性があります。

さらに、CAS はランダムを作成するときにも使用されます。

ここでは、デフォルトの Random シードが現在の時間に適していることもわかります。もちろん、シードはコンストラクターで指定することもできます。

 2.使用する

Random random = new Random();
random.nextInt(); //返回[0, max)的随机正整数
random.nextInt(10); //返回[0,10)的随机正整数
        
double rd1 = Math.random(); //返回[0.0,1.0)的随机小数

ThreadLocalRandom の概要

ThreadLocalRandom は、同じく弱い擬似乱数である Random を継承します。これは主に、高同時実行環境における Random の乱数生成パフォーマンスの問題を解決します。

1. ThreadLocalRandom がスレッドセーフであるのはなぜですか?

ThreadLocal の原理と同様に、現在の Thread オブジェクトの threadLocalRandomSeed 変数にランダム シードを保存するため、各スレッドが独自のランダム シードを持ち、スレッド レベルの分離が実現されるため、ThreadLocalRandom はスピン ロックと cas を渡す必要がありません。ランダム シードのスレッドの安全性を確保します。同時実行性が高いシナリオでは、効率は比較的高くなります。

2. ソースコードの実装:

ThreadLocalRandom は、安全でないメソッドを広範囲に使用して、アドレスを通じて変数を直接操作します。unfafe の使用法については、「3.0 Java マジック クラス: 安全でないアプリケーションの分析」を参照してください。note

1) シードは Thread クラスで定義され、ThreadLocalRandom で定義された SEED は Thread クラスの threadLocalRandomSeed 属性のメモリ オフセットを表します。

ThreadLocalRandom.java
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        SEED = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));
        PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

Thread.java
@sun.misc.Contended("tlr")
//当前Thread的随机种子 默认值是0
long threadLocalRandomSeed;
@sun.misc.Contended("tlr")
//用来标志当前Thread的threadLocalRandomSeed是否进行了初始化 0代表没有,非0代表已经初始化 默认值是0
int threadLocalRandomProbe;

注: @sun.misc.Contende は、誤った共有を避けるためのものです。

2) スレッドが ThreadLocalRandom の現在のメソッドを呼び出すと、ThreadLocalRandom は呼び出しスレッドの threadLocalRandomSeed 変数を初期化する、つまりシードを初期化する責任があります。

public static ThreadLocalRandom current() {
        // 获取当前线程的
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
}

static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
}

説明: unsafe の putLong メソッドを使用して、オブジェクト属性のオフセット アドレスに従ってメモリを直接操作します。

3) ThreadLocalRandom の nextInt メソッドが呼び出されると、現在のスレッドの threadLocalRandomSeed 変数が実際に現在のシードとして取得され、新しい値が計算され、新しいシードが現在のスレッドの threadLocalRandomSeed 変数に更新されます。(線形合同アルゴリズム: シードに従って乱数を計算し、次の計算のために乱数を新しいシードに更新します):

public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
        int r = mix32(nextSeed());
        int m = bound - 1;
        if ((bound & m) == 0) // power of two
            r &= m;
        else { // reject over-represented candidates
            for (int u = r >>> 1;
                 u + m - (r = u % bound) < 0;
                 u = mix32(nextSeed()) >>> 1)
                ;
        }
        return r;
}
final long nextSeed() {
        Thread t; long r; // read and update per-thread seed
        UNSAFE.putLong(t = Thread.currentThread(), SEED,
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
}

同時実行性が高い場合は、ThreadLocalRandom を使用して乱数を生成してみてください-Tencent Cloud Developer Community-腾讯云

3. 説明:

ssh アーキテクチャでは、Random も ThreadLocalRandom もグローバル変数で定義できませんが、メソッドで定義して初期化する必要があります。それ以外の場合は、異なるスレッドが同じシードを使用する可能性があり、作成される乱数のシーケンスが同じになる可能性があります。今すぐ:

java.Security.SecureRandom の概要

また、暗号化で使用できる強力な擬似乱数アルゴリズムを提供する java.util.Random も継承しています。

1. Linux PRNG (擬似乱数ジェネレーター) では乱数はどのように生成されますか?

Linux カーネルは、データのランダム性を記述するためにエントロピーを使用します。エントロピーは、システムの混乱と無秩序の程度を記述する物理量です。エントロピーが大きいほど、システムの秩序は悪くなります。カーネルは、デバイス ドライバーやその他のソースから周囲のノイズを収集するエントロピー プールを維持します。カーネル内の PRNG はキャラクター デバイス ランダムであり、ユーザー モード プロセスで使用する 2 つのキャラクター デバイス (/dev/random および /dev/urandom) を提供します。

  • /dev/random は、比較的高い乱数品質を必要とするリクエストに適しています。エントロピー プール内のデータが不十分な場合、dev/random デバイスを読み取ると、デバイス内のノイズの総数より少ないランダム バイトが返されます。エントロピープール。/dev/random は、非常にランダムな公開キーまたはワンタイム パッドを生成します。エントロピー プールが空の場合、周囲のノイズが十分に収集されるまで、/dev/random への読み取りはブロックされます。この設計により、/dev/random が真の乱数ジェネレーターとなり、ランダム データの可能な限り最大のエントロピーが提供されます。
  • /dev/urandom、エントロピー プール内のデータを再利用して擬似乱数データを生成するノンブロッキング乱数ジェネレーター。これは、/dev/urandom からの読み取りはブロックされませんが、その出力のエントロピーが /dev/random よりも小さい可能性があることを意味します。ほとんどのアプリケーションでランダム性が許容される場合、強度の低い暗号を生成するための擬似乱数ジェネレーターとして使用できます。

2. SecureRandom インスタンス メソッドを取得します。

SecureRandom インスタンスを取得するには 2 つの方法があります。

  • SecureRandom random = new SecureRandom(); //パラメータを通じてシードを指定することもできますが、これはお勧めできません。
  • ランダムランダム = SecureRandom.getInstanceStrong();

これら 2 つの方法の違い: Linux では、前者は擬似乱数ジェネレーターとして /dev/urandom を使用し、後者は擬似乱数ジェネレーターとして /dev/random を使用します。したがって、後者は、乱数を取得するために nextInt などのメソッドを呼び出すときに、システムのエントロピー プール内のデータが不十分なためにブロックされる可能性があります。

1) 実験:

import java.security.SecureRandom;
import java.util.Random;
import java.security.NoSuchAlgorithmException;
public class RandomTest {
    public static void main(String... str) throws NoSuchAlgorithmException {
        //SecureRandom random = new SecureRandom();
        Random random = SecureRandom.getInstanceStrong();
        int out = 0;
        for (int i = 0; i < 1<<20 ; i++) {
            out ^= random.nextInt();
        }
        System.out.println(out);
    }
}

プログラムは 2 つの異なる方法で SecureRandom インスタンスを作成し、乱数を 1,048,576 回生成します。Linux でテストした結果、6 行目を使用して SecureRandom インスタンスを作成すると、プログラムは 2 秒以内に実行されますが、7 行目を使用すると、プログラムがブロックされることがわかりました。これも上記の結論を裏付けます。

2) strace を介したシステムコールのトレース:

strace コマンドは Linux システムコールをトレースできます。したがって、上記のプログラムを次のように実行します。

A. 6 行目を使用して SecureRandom インスタンスを作成し、javac をコンパイルして次のように実行します。

javac RandomTest.java
strace -ff -o ./c.strace  java RandomTest
1839562559
#会在当前目录产生大量的strace文件,通过执行如下命令搜索"dev/random"所在的文件
grep "dev" -r -n ./
./c.strace.22057:6:openat(AT_FDCWD, "/sys/devices/system/cpu", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
./c.strace.22057:966:access("/dev/random", R_OK)             = 0
./c.strace.22057:967:access("/dev/random", R_OK)             = 0
./c.strace.22057:968:access("/dev/urandom", R_OK)            = 0
./c.strace.22057:969:open("/dev/random", O_RDONLY)           = 5
./c.strace.22057:970:fstat(5, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
./c.strace.22057:971:open("/dev/urandom", O_RDONLY)          = 6
./c.strace.22057:972:fstat(6, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
./c.strace.22057:973:access("/dev/random", R_OK)             = 0
./c.strace.22057:974:access("/dev/random", R_OK)             = 0
./c.strace.22057:975:open("/dev/random", O_RDONLY)           = 7
./c.strace.22057:976:fstat(7, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
./c.strace.22057:977:open("/dev/random", O_RDONLY)           = 8
./c.strace.22057:978:fstat(8, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
./c.strace.22057:979:access("/dev/urandom", R_OK)            = 0
./c.strace.22057:980:access("/dev/urandom", R_OK)            = 0
./c.strace.22057:981:open("/dev/urandom", O_RDONLY)          = 9
./c.strace.22057:982:fstat(9, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
./c.strace.22057:985:open("/dev/urandom", O_RDONLY)          = 10
./c.strace.22057:986:fstat(10, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
./c.strace.21889:96:fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
./c.strace.22065:10:open("/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 3

ファイルを開く

SecureRandom.java の最下層は 2 つのファイル /dev/random と /dev/urandom を開きますが、最終的に読み取られるのは 6 番の /dev/urandom ファイルであることがわかります。

B. 7 行目を使用して SecureRandom インスタンスを作成し、javac でコンパイルして、同じように実行します。

strace -ff -o c.strace  java RandomTest
#会一直在这里阻塞住,没有执行结果。。。

同じ方法でファイルを見つけて開きます。

SecureRandom.java の最下層は 2 つのファイル /dev/random と /dev/urandom を開きますが、最終的な読み取りは 8 番目の /dev/random ファイルと読み取りシステム コールであることがわかります。ブロックされました。

linux - Java SecureRandom はブロックしませんか? どうやって?- 情報セキュリティスタック交換

SecureRandom を使用して乱数マイニング レコードを生成する - Tencent Cloud Developer Community - Tencent Cloud

 

 

 

 

 

 

おすすめ

転載: blog.csdn.net/liuxiao723846/article/details/128698552