Slide 1

Slide 1 text

Java における 乱数生成器とのつき合い方 JJUG CCC 2019 fall / 2019-11-23 KOMIYA Atsushi

Slide 2

Slide 2 text

@komiya_atsushi

Slide 3

Slide 3 text

本日のお話

Slide 4

Slide 4 text

Java で 乱数を生成する (乱数生成器を利用する)

Slide 5

Slide 5 text

乱数生成器の 基礎知識

Slide 6

Slide 6 text

乱数生成器の種類

Slide 7

Slide 7 text

乱数生成器の種類: 真の乱数生成器 ● 予測不可能な乱数列を生成する乱数生成器 ● 専用のハードウェアが用いられたりする ○ 身近な例で言えば なども真の乱数生成器に該当する ● Linux の /dev/random は真の乱数生成器と呼べそう? ○ macOS は擬似乱数生成器 (Yarrow アルゴリズム) による実装らし い

Slide 8

Slide 8 text

乱数生成器の種類: 擬似乱数生成器 ● 決定論的乱数生成器 (DRBG) とも呼ばれる ● アルゴリズムによって乱数列が生成される ○ アルゴリズムと内部状態が既知なら、生成される乱数列を予測できる ○ 内部状態を特定の値に変更 (シードを再設定) することで乱数列を再現できる ● 周期が存在しうる ○ 周期が一巡すると、過去に生成したのと全く同じ乱数列が生成される ● 生成アルゴリズムの一例 ○ 線形合同法、メルセンヌツイスタ、Xorshift, SplitMix, PCG, ...

Slide 9

Slide 9 text

乱数生成器の種類: 暗号論的擬似乱数生成器 ● 擬似乱数生成器の一種 ● セキュリティに関わる場面での利用に適している ○ 例: パスワードをハッシュ化する際の salt の生成 ● 要求仕様がある ○ (1) next-bit test に合格する ○ (2) state compromise extension に耐える ○ 詳細は ja.wikipedia.org/wiki/暗号論的擬似乱数生成器 を参照

Slide 10

Slide 10 text

乱数生成器の性能評価

Slide 11

Slide 11 text

性能評価のポイント ● 乱数の生成速度 ● 内部状態のメモリフットプリント ● 乱数列の周期の長さ ● 生成される乱数列の統計的品質 ○ 生成される乱数に統計的な偏りがなく、一様に分布している ことが望ましい ○ ランダムであることを評価するための統計的仮説検定が存在 する

Slide 12

Slide 12 text

乱数生成器の統計的な欠陥が引き起こした事例 ja.wikipedia.org/wiki/線形合同法 > 短所 より引用 下位ビットのランダム性が低い。 Mの値によっては、最下位に近いビットはほとんどランダムでなく、完全に 規則性を持っていることすらある。たとえば Mが偶数の場合(コンピュータでの実装が楽であるために、 Mに 2の冪を採用した場合はこれに当たる)、 最下位ビットは、同じものが出続けるか、 0と1が交互にでるかの どちらかである。 ja.wikipedia.org/wiki/カルドセプト_サーガ > バグ問題 より引用 とくに深刻と言われるバグは、 ダイスの目が偶数・奇数を繰り返す というものであった。疑似乱数の生成な いし利用法に問題があったと推測されている(たとえばある種の線形合同法を不適切に用いると、下位 1 ビットは0と1を繰り返す)。

Slide 13

Slide 13 text

統計的な品質評価 ● 代表的なテストスイート ○ Diehard (1995) ○ TestU01 (2007) ■ Small Crush / Crush / Big Crush の三種がある ○ PractRand (2010?) ● いずれのテストスイートも数多くの統計的仮説検定の組み合わせで構 成されている ● 最近では、TestU01 の Big Crush がデファクトスタンダードとして利用 されている印象がある ○ Big Crush の fail が 0 個であることを謳う乱数生成器の実装を見かける

Slide 14

Slide 14 text

Java における 乱数生成器

Slide 15

Slide 15 text

Java クラスライブラリが提供する乱数生成器 ● 乱数生成器のクラス ○ java.util.Random ○ java.security.SecureRandom ○ java.util.concurrent.ThreadLocalRandom ○ java.util.SplittableRandom ○ これらクラスの継承関係が少し (次頁参照) ● 乱数生成メソッド ○ Math.random()

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

java.util.Random クラス ● 線形合同法を利用した擬似乱数生成器 ● 一言でいうなれば「レガシー」 ○ 統計的品質がよろしくない ■ TestU01 の Big Crush で 50 個近く fail する ■ commons.apache.org/proper/commons-rng/userguide/rng.html#a 5._Quality を参照 ○ 乱数生成の速度がよろしくない ■ 内部状態の更新に AtomicLong の操作が必要になるため ■ 単一スレッドで利用していても遅い ■ 同一インスタンスを複数スレッドで扱うと悲惨なことになる

Slide 18

Slide 18 text

protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits)); } 参考: Random#next(int) メソッドの実装 線形合同法そのものの 処理はとてもシンプル 下位ビットの利用を避 けている

Slide 19

Slide 19 text

// シードを指定してインスタンスを生成する Random jur = new Random(123); System.out.println(jur.nextInt()); // => -1188957731 // シードを (再) 設定する jur.setSeed(456); System.out.println(jur.nextInt()); // => -1035442920 Random クラスの利用例

Slide 20

Slide 20 text

SecureRandom クラス ● 暗号論的擬似乱数生成器の実装 ● 具体的な乱数生成アルゴリズムは複数存在する (後述) ○ SecureRandom.getInstance(String) でアルゴリズム名を引数に指 定し、インスタンスを得る ● Web アプリケーションのセッション ID やパスワードの salt など、 推測されたくないランダム文字列の生成などで利用するのに適し ている ○ 実際に、Tomcat のセッション ID (jsessionid) の生成において SecureRandom が使われている

Slide 21

Slide 21 text

SecureRandom で利用できるアルゴリズム ● プラットフォーム (OS) によって、標準で利用できるアルゴリズムが 異なる ○ Java 8 時点でのプラットフォーム別アルゴリズム ■ docs.oracle.com/javase/jp/8/docs/technotes/guides/security/SunPr oviders.html#SecureRandomImp ○ Java 11 で利用できるアルゴリズムの一覧 ■ docs.oracle.com/javase/jp/11/docs/specs/security/standard-names.ht ml#securerandom-number-generation-algorithms ● プラットフォームおよびアルゴリズムの組み合わせ次第で、 乱数生成時にブロッキングが生じることに注意する必要がある

Slide 22

Slide 22 text

SecureRandom で利用できるアルゴリズム ● NativePRNG / NativePRNGBlocking / NativePRNGNonBlocking ○ /dev/random や /dev/urandom を利用して擬似乱数を生 成するアルゴリズム ○ UNIX 系プラットフォームで利用できる ○ SecureRandom#setSeed() による、再現性のある乱数生 成はできない様子 ■ 少なくとも macOS ではできないことを確認

Slide 23

Slide 23 text

SecureRandom で利用できるアルゴリズム generateSeed() nextBytes() NativePRNG /dev/random /dev/urandom NativePRNGBlocking /dev/random /dev/random NativePRNGNonBlocking /dev/urandom /dev/urandom

Slide 24

Slide 24 text

SecureRandom で利用できるアルゴリズム ● DRBG (Java 9~, JEP 273) ○ NIST SP 800-90 Ar1 で規格化されたアルゴリズム ■ nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800- 90Ar1.pdf 参照 ○ DRBG の具体的なアルゴリズムは、システムプロパティ securerandom.drbg.config で指定する ■ Security.setProperty("securerandom.drbg.config", "CTR_DRBG");

Slide 25

Slide 25 text

SecureRandom で利用できるアルゴリズム ● SHA1PRNG ○ レガシーだが、すべてのプラットフォームで利用できる ○ (参考) Android では deprecated になっている ■ android-developers.googleblog.com/2016/06/securit y-crypto-provider-deprecated-in.html 参照 ● その他 (詳しく知らないので割愛) ○ PKCS11 ○ Windows-PRNG

Slide 26

Slide 26 text

// アルゴリズムを明示的に指定してインスタンスを取得する SecureRandom sr = SecureRandom.getInstance("DRBG"); sr.setSeed(789); System.out.println(sr.nextInt()); // => -1542194244 sr.setSeed(789); sr = SecureRandom.getInstance("NativePRNGNonBlocking"); System.out.println(sr.nextInt()); // 出力される値は不定 SecureRandom クラスの利用例

Slide 27

Slide 27 text

ThreadLocalRandom クラス ● マルチスレッドでの利用に適した擬似乱数生成器の実装 ○ スレッドごとに固有の擬似乱数生成器 (厳密には内部状態) が存在する ○ 利用する際に明示的なインスタンス生成が不要である ● SplittableRandom クラスと同じ SplitMix アルゴリズムを利用し ている (Java 8 以降) ○ Java 7 では、Random クラスと同じ線形合同法を採用していた ● Random#setSeed() を呼び出すことができないため、乱数生成の 再現性が求められるケースでの利用には適さない ○ UnsupportedOperationException が発生する

Slide 28

Slide 28 text

// インスタンスの生成は不要 System.out.println(ThreadLocalRandom.current().nextInt()); // ThreadLocalRandom.current() で取得したオブジェクトを // 他のスレッドで利用するのはご法度 // シードを設定しようとすると例外が発生する try { ThreadLocalRandom.current().setSeed(123); } catch (UnsupportedOperationException e) { e.printStackTrace(); } ThreadLocalRandom クラスの利用例

Slide 29

Slide 29 text

SplittableRandom クラス ● Fork/Join フレームワークを含むマルチスレッドでの利用に適した 擬似乱数生成器 ● SplittableRandom#split() により、現在の乱数生成器と完全 に異なる乱数列を生成するインスタンスを生成できる ○ 複数スレッドで並列して乱数生成する際に、まったく同じ乱数列が生成される と困る場合に重宝する ○ ThreadLocalRandom は異なるスレッドでも同じ乱数列を生成しうる ○ ForkJoinTask の fork() にあわせて split するなどの使い方がある ● Random クラスを継承していないことに注意が必要

Slide 30

Slide 30 text

// java.util.Random と同様に // コンストラクタでインスタンスを生成する SplittableRandom spr = new SplittableRandom(); System.out.println(spr.nextInt()); // split された乱数生成器は、異なる乱数列を生成する SplittableRandom another = spr.split(); System.out.println(another.nextInt()); SplittableRandom クラスの利用例

Slide 31

Slide 31 text

Math.random() メソッド ● [0, 1) の区間の double 型の乱数を生成する ● 乱数生成器のインスタンスを明示的に生成することなく気軽に使 える ● Math クラス内部で一つの Random オブジェクトを保有している ○ 複数スレッドからこのメソッドを並列して呼び出すと、速度パフォーマンス 的に悲惨なことになる ● これもレガシーである ○ ThreadLocalRandom.current().nextDouble() で代替できる

Slide 32

Slide 32 text

Java クラスライブラリ **以外** の手段

Slide 33

Slide 33 text

Apache Commons RNG ● commons.apache.org/proper/commons-rng/ ● Apache Commons Math に実装されていた乱数生成関連の 機能が切り出され、また速度性能面で改良が施されている ● 豊富な擬似乱数生成器の実装と、多種多様な確率分布から のサンプリングを提供する ○ シミュレーション目的で乱数生成したい場合に適している ● 提供される API は java.util.Random クラスを継承していないことに注 意

Slide 34

Slide 34 text

Amazon Corretto Crypto Provider (ACCP) ● Amazon による Java Cryptography Architecture (JCA) の実装 ○ aws.amazon.com/jp/blogs/opensource/introducing-amazon-corretto-cry pto-provider-accp/ 参照 ● 暗号化などの処理における速度パフォーマンスの改善が期待でき る ● SecureRandom のアルゴリズム実装として、DRBG (AES-CTR-256) が提供されている ○ x86-64 の RDRAND 命令が利用できる必要がある ● 利用できるプラットフォームは Linux のみ?

Slide 35

Slide 35 text

乱数生成器の実装を 使い分ける

Slide 36

Slide 36 text

利用目的に適した乱数生成器を選ぶ ● セキュリティに関わる場面で利用する? ○ 暗号論的擬似乱数生成器を選ぶべき ● シミュレーション目的で利用する? ○ 周期が長い擬似乱数生成器 (メルセンヌツイスタ など) が適している ● Web アプリケーションなど低レイテンシが求められる場面で利 用する? ○ 乱数生成が高速かつメモリフットプリントの小さい擬似乱数生成器 (SplitMix など) が適している

Slide 37

Slide 37 text

乱数生成器の **実装** を選ぶ ● セキュリティ用途 ○ SecureRandom クラス ○ アルゴリズム選定はプラットフォームに依存する ○ ブロッキングの有無に注意する ● シミュレーション ○ Apache Commons RNG の メルセンヌツイスタ実装など ● Web アプリケーション ○ ThreadLocalRandom クラス ○ 再現性が求められるなら SplittableRandom なども

Slide 38

Slide 38 text

まとめ

Slide 39

Slide 39 text

まとめ ● 擬似乱数生成器の特性を理解する ○ 生成速度、メモリフットプリント、周期、統計的品質 ○ セキュリティ用途で利用できるか ● 乱数の利用用途に従って、適切な乱数生成器とその実 装を選ぶ ● Java にはレガシーな乱数生成関連の実装があること に留意する

Slide 40

Slide 40 text

Thanks!