995
京東網上商城
JVM上的隨機數與熵池策略
在apache-tomcat官方文檔:如何讓tomcat啟動更快 裏麵提到了一些啟動時的優化項,其中一項是關於隨機數生成時,采用的“熵源”(entropy source)的策略。
他提到tomcat7的session id的生成主要通過java.security.SecureRandom
生成隨機數來實現,隨機數算法使用的是”SHA1PRNG”
private String secureRandomAlgorithm = "SHA1PRNG";
在sun/oracle的jdk裏,這個算法的提供者在底層依賴到操作係統提供的隨機數據,在linux上,與之相關的是/dev/random
和/dev/urandom
,對於這兩個設備塊的描述以前也見過討論隨機數的文章,wiki中有比較詳細的描述,摘抄過來,先看/dev/random
:
在讀取時,/dev/random設備會返回小於熵池噪聲總數的隨機字節。/dev/random可生成高隨機性的公鑰或一次性密碼本。若熵池空了,對/dev/random的讀操作將會被阻塞,直到收集到了足夠的環境噪聲為止
而 /dev/urandom
則是一個非阻塞的發生器:
dev/random的一個副本是/dev/urandom (”unlocked”,非阻塞的隨機數發生器),它會重複使用熵池中的數據以產生偽隨機數據。這表示對/dev/urandom的讀取操作不會產生阻塞,但其輸出的熵可能小於/dev/random的。它可以作為生成較低強度密碼的偽隨機數生成器,不建議用於生成高強度長期密碼。
另外wiki裏也提到了為什麼linux內核裏的隨機數生成器采用SHA1散列算法而非加密算法,是為了避開法律風險(密碼出口限製)。
回到tomcat文檔裏的建議,采用非阻塞的熵源(entropy source),通過java係統屬性來設置:
-Djava.security.egd=file:/dev/./urandom
這個係統屬性egd表示熵收集守護進程(entropy gathering daemon),但這裏值為何要在dev
和random
之間加一個點呢?是因為一個jdk的bug,在這個bug的連接裏有人反饋及時對 securerandom.source 設置為 /dev/urandom
它也仍然使用的 /dev/random
,有人提供了變通的解決方法,其中一個變通的做法是對securerandom.source設置為 /dev/./urandom
才行。也有人評論說這個不是bug,是有意為之。
我看了一下我當前所用的jdk7的java.security文件裏,配置裏仍使用的是/dev/urandom
:
#
# Select the source of seed data for SecureRandom. By default an
# attempt is made to use the entropy gathering device specified by
# the securerandom.source property. If an exception occurs when
# accessing the URL then the traditional system/thread activity
# algorithm is used.
#
# On Solaris and Linux systems, if file:/dev/urandom is specified and it
# exists, a special SecureRandom implementation is activated by default.
# This "NativePRNG" reads random bytes directly from /dev/urandom.
#
# On Windows systems, the URLs file:/dev/random and file:/dev/urandom
# enables use of the Microsoft CryptoAPI seed functionality.
#
securerandom.source=file:/dev/urandom
我不確定jdk7裏,這個 /dev/urandom
也同那個bug報告裏所說的等同於 /dev/random
;要使用非阻塞的熵池,這裏還是要修改為/dev/./urandom
呢,還是jdk7已經修複了這個問題,就是同注釋裏的意思,隻好驗證一下。
使用bug報告裏給出的代碼:
import java.security.SecureRandom;
class JRand {
public static void main(String args[]) throws Exception {
System.out.println("Ok: " +
SecureRandom.getInstance("SHA1PRNG").nextLong());
}
}
然後設置不同的係統屬性來驗證,先是在我的mac上:
% time java -Djava.security.egd=file:/dev/urandom JRand
Ok: 8609191756834777000
java -Djava.security.egd=file:/dev/urandom JRand
0.11s user 0.03s system 115% cpu 0.117 total
% time java -Djava.security.egd=file:/dev/./urandom JRand
Ok: -3573266464480299009
java -Djava.security.egd=file:/dev/./urandom JRand
0.11s user 0.03s system 116% cpu 0.116 total
可以看到/dev/urandom
和 /dev/./urandom
的執行時間差不多,有點納悶,再仔細看一下wiki裏說的:
FreeBSD操作係統實現了256位的Yarrow算法變體,以提供偽隨機數流。與Linux的/dev/random不同,FreeBSD的/dev/random不會產生阻塞,與Linux的/dev/urandom相似,提供了密碼學安全的偽隨機數發生器,而不是基於熵池。而FreeBSD的/dev/urandom則隻是簡單的鏈接到了/dev/random。
盡管在我的mac上/dev/urandom
並不是/dev/random
的鏈接,但mac與bsd內核應該是相近的,/dev/random
也是非阻塞的,/dev/urandom
是用來兼容linux係統的,這兩個隨機數生成器的行為是一致的。參考這裏。
然後再到一台ubuntu係統上測試:
% time java -Djava.security.egd=file:/dev/urandom JRand
Ok: 6677107889555365492
java -Djava.security.egd=file:/dev/urandom JRand
0.14s user 0.02s system 9% cpu 1.661 total
% time java -Djava.security.egd=file:/dev/./urandom JRand
Ok: 5008413661952823775
java -Djava.security.egd=file:/dev/./urandom JRand
0.12s user 0.02s system 99% cpu 0.145 total
這回差異就完全體現出來了,阻塞模式的熵池耗時用了1.6秒,而非阻塞模式則隻用了0.14秒,差了一個數量級,當然代價是轉換為對cpu的開銷了。
// 補充,連續在ubuntu上測試幾次/dev/random
方式之後,導致熵池被用空,被阻塞了60秒左右。應用服務器端要避免這種方式。
最後更新:2017-05-23 19:31:55