【Java】 暗号用乱数 ~ SecureRandom ~

■ はじめに

SSL通信では、
以下「SSLシーケンス概要」の★部分にあるように乱数を作成してやり取りする。
今回は、Javaでの実装でこの乱数作成について考える

# 本当は、以下のトラブルを解決する過程で、SecureRandom クラスを学ばなければならなかったので
# トラブルシューティングの前段階として、知識を纏めてみた

SSLシーケンス概要
http://itpro.nikkeibp.co.jp/article/COLUMN/20060731/244715/?rt=nocnt

より

               Client                                     Server
               |                                          |
               |  httpsでサーバに対してリクエストを出す   |
                  |----------------------------------------->|
                 |                                          |
               |            サーバ証明書を送付            |
               |<-----------------------------------------|
 証明書をチェック     |                                          |
               |                                          |
 ★乱数作成★       |                                          |
               |                                          |
 サーバ証明書の      |                                          |
 公開鍵で乱数を暗号化  |                                          |
                     |                                          |
               |           暗号化した乱数を送信           |
                  |----------------------------------------->|
乱数を元に共通鍵を作成 |                                          | 乱数を元に共通鍵を作成
               |                                          |
               |  共通暗号鍵方式でデータを暗号化して通信  |
                  |<---------------------------------------->|
               |                                          |

【1】暗号用乱数生成方法

[1] SecureRandom.getInstanceStrong() で作成する(From Java1.8)

* プラットフォームでもっとも強力なアルゴリズムを使用して
 SecureRandomインスタンスを作成

[2] SecureRandom.getInstance(【アルゴリズム】)で作成する

* 【アルゴリズム】:"NativePRNGBlocking"/"NativePRNGNonBlocking"/"NativePRNG"/"SHA1PRNG" など
* OSによって、サポートするアルゴリズムに差異があるので注意

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp
[3] new SecureRandom()で作成する
[4] 「-Dsecurerandom.source=file:/dev/urandom」or 「System.setProperty("securerandom.source", "file:/dev/urandom");」

* 「securerandom.source=file:/dev/random」だと「/dev/random」の性質上、
 ブロックする可能性があるので注意

http://otndnld.oracle.co.jp/document/products/E13153_01/wlcp/wlss40/configwlss/jvmrand.html
http://qa.atmarkit.co.jp/q/1901

【2】どの方法がいい?

ずるい結論かもしれないが、結局は...

 * 開発環境だけでなく、本番を想定した環境で試したうえで決める
  => 一見、「SecureRandom.getInstanceStrong()を指定しとけばいい」って思ったが、
     ハードウェア(PC/Mobile/Raspberry PI etc...)・OS環境・使用する証明書・暗号アルゴリズムなどによっては、
     パフォーマンスに影響するので、しっかり実環境で試した方がいい

選定の仕方

i) まず、Java1.8以上であれば「[1] SecureRandom.getInstanceStrong()」を使って、実環境で確認する
   => 特に問題がなければ、OK!
   => (実際に起こった現象なのだが)SecureRandom.getInstanceStrong()を使用して
      リクエストタイムアウトでSSL通信できなかった。
      (必ずしも、SecureRandom.getInstanceStrong()を使えば正解って訳じゃない)

ii) i)で問題がある又はJava1.7以下の環境の場合は、
   「[2] SecureRandom.getInstance(【アルゴリズム】)」で直接アルゴリズムを指定するか
   「[3] new SecureRandom()」を使用し、実環境で確認する

※ 以下のサイトが非常に参考になる

http://qiita.com/aibax/items/29e09318ac85c4e93c1a

参考文献

Oracleドキュメント API「SecureRandom」仕様
https://docs.oracle.com/javase/jp/8/docs/api/java/security/SecureRandom.html
OSで使用できるアルゴリズムと優先順位の一覧
https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp
https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/SunProviders.html#SunPKCS11Provider
乱数生成に伴う JVM の遅延の回避
http://otndnld.oracle.co.jp/document/products/E13153_01/wlcp/wlss40/configwlss/jvmrand.html
「/dev/random」「/dev/urandom」
http://piyopiyocs.blog115.fc2.com/blog-entry-665.html
http://myokota.hatenablog.jp/entry/2015/01/25/214528
その他サイト
http://blog.64p.org/entry/2014/10/08/233604
http://qiita.com/aibax/items/29e09318ac85c4e93c1a
http://megalodon.jp/2014-1009-0140-17/blog.64p.org/entry/2014/10/08/233604
https://gist.github.com/buzztaiki/ca89a079b093d716ef94

関連記事

Webサービス / Metro [8] ~ SSL通信を行う (2) / クライアントサイド ~
WebサービスにアクセスするJavaクライアントで、Exception: Broken pipeが発生する
SSL / TLS ~知識編~