SecureRandom.getInstanceStrong() で使われる乱数生成のアルゴリズムを調べてみた


# 概要

Java 8 から java.security.SecureRandom クラスに追加された getInstanceStrong() というメソッドについて調べてみました。


# SecureRandom とは

暗号鍵の生成などで安全に使える乱数を生成するための java.security.SecureRandom というクラスがあります。

この SecureRandom を使う場合、Java 7 以前はプラットフォームごとのデフォルトのアルゴリズムで使用するか、明示的に使用するアルゴリズムを指定していました。

Java 8 からは実行するプラットフォームで使える一番安全なアルゴリズムで初期化されたインスタンスを返す getInstanceStrong() というメソッドが追加されたので、その挙動を調べてみました。

# テストコード

```java:SecureRandomTest.java
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class SecureRandomTest
{
    public static void main(String[] args)
    {
        try
        {
            SecureRandom random = SecureRandom.getInstanceStrong();
            System.out.println("SecureRandom.getInstanceStrong() = " + random.getAlgorithm());
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
    }
}
```

# 実行結果

## Amazon Linux

AmazonLinux 2016.03 + OpenJDK 1.8.0_91
=> SecureRandom.getInstanceStrong() = NativePRNGBlocking

## CentOS

CentOS Linux 7.2.1511 + OpenJDK 1.8.0_91
=> SecureRandom.getInstanceStrong() = NativePRNGBlocking

## Mac
OS X El Capitan 10.11.5 + Java 1.8.0_65
=> SecureRandom.getInstanceStrong() = NativePRNGBlocking

## Windows
Windows 8.1 Professional + Java 1.8.0_92
=> SecureRandom.getInstanceStrong() = Windows-PRNG

'NativePRNGNonBlocking' を明示的に指定すると java.security.NoSuchAlgorithmException が発生



# 使用できるアルゴリズムと優先順位

各プラットフォームで使用できるアルゴリズムとデフォルトの優先順位はこのドキュメントにある一覧で調べることができます。

[Java暗号化アーキテクチャOracleプロバイダのドキュメント(JDK 8用)](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp)


しかし、getInstanceStrong() で選択されるアルゴリズムはこの優先順位とは異なっています。

getInstanceStrong() の優先順位は ${JAVA_HOME}/jre/lib/security/java.security の securerandom.strongAlgorithms というプロパティに定義されています。

## AmazonLinux
AmazonLinux 2016.03 + OpenJDK 1.8.0_91
> securerandom.strongAlgorithms=NativePRNGBlocking:SUN


## CentOS

CentOS Linux 7.2.1511 + OpenJDK 1.8.0_91
> securerandom.strongAlgorithms=NativePRNGBlocking:SUN

## Mac

OS X El Capitan 10.11.5 + Java 1.8.0_65
> securerandom.strongAlgorithms=NativePRNGBlocking:SUN

## Windows
Windows 8.1 Professional + Java 1.8.0_92
> securerandom.strongAlgorithms=Windows-PRNG:SunMSCAPI,SHA1PRNG:SUN


# 【考察】

## 常に getInstanceStrong() がベストなのか？

Java 8 から SecureRandom による乱数の生成に以下のアルゴリズムが追加されました。

- NativePRNG
- NativePRNGBlocking
- NativePRNGNonBlocking

このうち Linux や OS X で getInstanceStrong() を実行したときに返ってくる NativePRNGBlocking は、乱数の生成に /dev/random を使用します。

/dev/random は安全な乱数を生成するために十分な量のエントロピーが無い場合は処理がブロックされる（エントロピーが溜まるまで環境ノイズを収集している？）ので、乱数を多く生成する必要がある場合はパフォーマンスの低下に注意が必要です。

これらの新しく追加されたアルゴリズムの実装であるSUNプロバイダが使用する乱数生成器の組み合わせです。

| アルゴリズム | generateSeed() | nextBytes() |
|:-----------|:-----------|:-----------|
| NativePRNG | /dev/random | /dev/urandom |
| NativePRNGBlocking | /dev/random | /dev/random |
| NativePRNGNonBlocking | /dev/urandom | /dev/urandom 

[Java暗号化アーキテクチャOracleプロバイダのドキュメント(JDK 8用)](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/SunProviders.html#SUNProvider)

このように、処理がブロックされない /dev/urandom を組み合わせて使用できるような選択肢が用意されています。

安全な乱数を一つだけ欲しいという場合は getInstanceStrong() を使い、乱数の生成頻度が高くなるに従って、安全面とパフォーマンスのトレードオフを考えながら NativePRNGBlocking > NativePRNG > NativePRNGNonBlocking と選択していくのが良いのではないでしょうか。

## Windows の場合

NativePRNG のアルゴリズムは Windows 版のSUNプロバイダには実装されていないので、明示的に指定したコードを Windows で実行するとjava.security.NoSuchAlgorithmException がスローされます。


## 感想

アルゴリズムを明示的に指定して使おうと思うと、開発環境が Windows で実行環境が Linux みたいなケースでは不便。

現実的な解決方法としては

- 乱数の生成頻度が低い: プラットフォームのデフォルトのアルゴリズムか getInstanceStrong() を使う
- 乱数の生成頻度が高い: 運用時はプラットフォーム毎に最適なアルゴリズムの SecureRandom を返す独自のファクトリクラスを実装する

といったところか。

SecureRandom.getInstanceStrongNonBlocking() のようなメソッドがあるとよいのだけれど。