SecureRandom的江湖偏方与真实效果

SecureRandom,我们一般都知道江湖偏方 -Djava.security=file:/dev/./urandom,但往往不求甚解,一年前,在那个有点暗的办公室里,我就是这么做的。

一年后,又有同学说JDK8下,好像Thread Dump出来了很多SecureRandom的BLOCKING。现在已习惯彻底解决问题了,于是怒翻JDK代码,并配合JMH写的测试,总结出这么一篇。

  1. /dev/random 与 /dev/urandom
    Linux的两个随机数源, 从IO中断,网卡传输包这些外部入侵者不可预测的随机源中获取熵,混合后使用CSPRNG生成熵池。当熵池估值为0时,/dev/random 会block住请求,而/dev/urandom 则会继续输出随机数。

  2. JDK7的SecureRandom
    2.1 generateSeed()与next*()
    generateSeed()可以为其他安全算法生成种子,随机度需求更高些。

nextInt(), nextLong()则是我们更关心的生成随机数,最后都是调用nextBytes。在某些情况

2.2 seedSource
seedSource由两者决定,首先看 -Djava.security.egd ,

没设置则看$JAVA_HOME/jre/lib/security/java.security,JDK7中securerandom.source=file:/dev/urandom

2.3 SHA1PRNG 与 NativePRNG
从名字就知道,两者都是伪随机算法。另外,两种算法都会有synchronized关键字,都会有阻塞,只有时间长短的不同。

在JMH的测试里,SHA1PRNG比Native快一倍,并且偶发的高延时更少一点。

2.3.1 SHA1PRNG
generateSeed的实现:只要配置的seedSource是/dev/random 或 /dev/urandom,就会使用NativeSeedGenerator,而此君在JDK7里有bug,只从/dev/random 取值,可能被阻塞。

nextBytes的实现: 纯Java实现,通过不断的对当前Hash值进行再一次SHA1哈希而成。

那Hash的初始值怎么来?如果没有被外部显式设置,则用下面比较复杂的算法生成。

先有个SecureRandom seeder,并且用java从系统收集到一些噪音作为这个SR的初始seed。
调用generateSeed() , 获得一个seed,可能被阻塞(见上)。
调用seeder.setSeed(seed) ,合并1和2的seed。
最后调用seeder.nextBytes(),生成最后的seed。
2.3.2 NativePRNG
generateSeed的实现:从/dev/random中取值,可能阻塞。

nextBytes的实现:从/dev/urandom 中取值,再XOR SHA1PRNG生成的随机值而成。

可见NativePRGN的性能一定会比单纯SHA1PRNG差。

那为什么要XOR SHA1PRNG呢?为了支持setSeed(),/dev/[u]random都是不可写的,只好再引入一个可设置seed的SHA1PRNG。

/dev/[u]random不需要Java应用来給种子,而SHA1PRNG则从/dev/urandom中获得种子并显式设置,也就不需要2.3.1中所述的种子四步曲,所以不会阻塞。

2.2.3 算法的选择
如果用getInstance()获取,则返回的是特定算法的实现,比如SecureRandom.getInstance(”SHA1PRNG”)

如果用new SecureRandom(), 则看seedSource的设置,如果是/dev/[u]random 之一则是NativePRNG,否则是SHA1PRNG,比如-Djava.security=file:/dev/./urandom,比/dev/urandom 多了个./在中间, 就成了SHA1PRNG了。

  1. 江湖偏方的诞生
    在JDK7,默认算法是NativePRNG,里面/dev/urandom本身不用seed,而用到的SHA1PRNG的初始seed从/dev/urandom 读取,不存在启动慢的问题。就是消耗比纯SHA1PRNG大一倍。

然后Tomcat 生成sessionId时显式使用了SHA1PRNG,因为NativeSeedGenerator的bug(见2.3.1),此时初始seed要从/dev/random读取,就会启动慢,所以要设置seedSoure而且要加个./在中间,绕过NativeSeedGenerator改为用URLSeedGenderator。

如果一个不明真相的群众,也跟着设置-Djava.security=file:/dev/./urandom, 一个意外的效果就是,默认的算法也变成SHA1PRNG了。

  1. JDK8的SecureRandom
    首先,Native算法多了两种子类型。原来的generateSeed从/dev/random,nextBytes从/dev/urandom, 而NativeBlocking则generateSeed 和 nextBytes 都从/dev/random中读,NativeNonBlocking则两者都从/dev/urandom中读。不过Native里nextBytes并不需要调用generateSeed,所以对于主要用SecureRandom来生成随机数的应用来说,这个区别不大。

其次,SHA1PRNG用到的SeedGenerator,终于改好了,原来NativeSeedGenerator无论设什么都是读/dev/random,现在改为设什么就读什么,所以jre/lib/security/java.security 里的 securerandom.source变为了 file:/dev/random

所以,JDK8里,如果你显式获得的SHA1PRNG以后启动不想被阻塞,还是要设成-Djava.security=file:/dev/urandom,只是不用猥琐的加个. 在中间了。不过为了兼容JDK7,依然加上也无不可。

如果你想把默认算法搞成SHA1PRNG,那还是要继续江湖偏方,-Djava.security=file:/dev/./urandom

  1. 结论
    SHA1PRNG 比 NativePRNG消耗小一半,偶发高延时也更少,没特殊安全要求的话可以用SHA1。
    如果想用SHA1, 设成-Djava.security=file:/dev/./urandom总是对的
    如果想用Native,什么都不设置就好了。如果你会用SecureRandom()为其他算法生成seed又不想被堵塞,则创建NativeNonBlocking
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值