导致Java SecureRandom线程阻塞问题
由于Linux操作系统熵值不够,导致使用安全随机数SecureRandom时,长时间线程阻塞,产生性能低下的问题。
问题定位
跟踪代码,发现线程堆栈停在SecureRandom.nextBytes()上,出现阻塞状态。表现为程序长时间无法向下运行。
问题根因
SecureRandom安全随机数会使用linux的随机数生成器(在linux上实际上使用的是/dev/random或者/dev/urandom)来生成安全随机数,即提供永不为空的随机字节数据流。默认会使用阻塞算法获取随机数,如果熵值不够则会导致长时间无法获取随机数,导致线程阻塞。
知识扩展
随机数产生的原理
为了尽可能的做到随机,随机数生成器会收集系统环境中各种数据,比如:鼠标的移动,键盘的输入, 终端的连接以及断开,音视频的播放,系统中断,内存 CPU 的使用等等。
生成器把收集到的各种环境数据放入一个池子 ( 熵池 ) 中,然后将这些数据进行去偏、漂白,主要目的也是使得数据更加无序,更加难以猜测或者预料得到。
有了大量的环境数据之后,每次获取随机数时,从池子中读取指定的字节序列,这些字节序列就是生成器生成的随机数。
随机数生成器结构
- /dev/random:随机性高,阻塞,真随机。
- /dev/urandom:随机性稍差,非阻塞,可能产生伪随机。
- 因此当有强随机性要求(密码,安全要求)时需要使用/dev/random。
使用SecureRandom一定获取到安全随机数吗?
SecureRandom使用的到底是/dev/random还是/dev/urandom呢?
查看SecureRandom源码
public SecureRandom() {
super(0);
// 默认获取伪随机生成器
getDefaultPRNG(false, null);
}
如果直接使用SecureRandom random = new SecureRandom()无法保证安全随机,那么如何获取安全随机数呢?SecureRandom提供了方法getInstanceStrong()。
public static SecureRandom getInstanceStrong() throws NoSuchAlgorithmException {
String property = AccessController.doPrivileged(
new PrivilegedAction<String>() {
@Override
public String run() {
return Security.getProperty(
"securerandom.strongAlgorithms");
}
});
.....
}
此方法获取的是/jre/lib/security/java.security配置文件中的securerandom.strongAlgorithms配置的算法,默认配置为阻塞的安全随机算法:
#
# A list of known strong SecureRandom implementations.
#
# To help guide applications in selecting a suitable strong
# java.security.SecureRandom implementation, Java distributions should
# indicate a list of known strong implementations using the property.
#
# This is a comma-separated list of algorithm and/or algorithm:provider
# entries.
#
securerandom.strongAlgorithms=NativePRNGBlocking:SUN
这也是为什么网上很多教程说使用下面方法规避:
#错误方法一:增加JVM参数
-Djava.security.egd=file:/dev/./urandom
#错误方法二:改JRE配置文件 配置文件:$JAVA_HOME/jre/lib/security/java.security
securerandom.source=file:/dev/urandom
但是从getInstanceStrong()方法以及securerandom.strongAlgorithms配置可知,默认仍为阻塞的安全随机算法,不能解决问题。应使用如下解决方案
解决方法
当使用安全随机数时,即使用类似如下代码时,
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(bytes);
可能会由于系统熵值不够,导致线程长时间等待,假死。
解决方法一:增加linux系统熵值
查看系统熵池的容量
cat /proc/sys/kernel/random/poolsize
查看系统熵池中拥有的熵数:一般为1000以上
cat /proc/sys/kernel/random/entropy_avail
查看从熵池中读取熵的阀值,当 entropy_avail 中的值少于这个阀值,读取 /dev/random 会被阻塞:
cat /proc/sys/kernel/random/read_wakeup_threshold
安装haveged服务给linux补熵。
安装haveged
yum install haveged -y
执行以下命令,开启服务开机自启动。
systemctl start haveged
systemctl enable haveged
执行以下命令,查看状态。
systemctl status haveged
解决方法二:使用伪随机临时规避
只有将securerandom.strongAlgorithms改为非阻塞的算法才能规避此问题,但是也将系统由安全随机改为伪随机,不能用于生产环境。
securerandom.strongAlgorithms=NativePRNGNonBlocking:SUN