Linux主机熵值不足导致SecureRandom线程阻塞问题
linux操作系统熵值不够,导致使用安全随机数时,长时间线程阻塞。
问题定位
使用jdk远程debug,发现线程堆栈停在SecureRandom.nextBytes()
上,出现阻塞状态。表现为程序长时间无法向下运行。
问题根因
jre的安全随机数会使用linux的随机数生成器(在linux上实际上使用的是/dev/random或者/dev/urandom)来生成安全随机数,即提供永不为空的随机字节数据流。默认会使用阻塞算法获取随机数,如果熵值不够则会导致长时间无法获取随机数,导致线程阻塞。
知识扩展
随机数产生的原理
为了尽可能的做到随机,随机数生成器会收集系统环境中各种数据,比如:鼠标的移动,键盘的输入, 终端的连接以及断开,音视频的播放,系统中断,内存 CPU 的使用等等
生成器把收集到的各种环境数据放入一个池子 ( 熵池 ) 中,然后将这些数据进行去偏、漂白,主要目的也是使得数据更加无序,更加难以猜测或者预料得到。
有了大量的环境数据之后,每次获取随机数时,从池子中读取指定的字节序列,这些字节序列就是生成器生成的随机数
随机数生成器结构
/dev/random和/dev/urandom都是linux的随机数生成器,但是二者也不相同。/dev/random是阻塞的,当系统熵值不足时会一直阻塞,尝试获取随机数的线程就会等待,直到熵值足够,所以也被称为真随机。而/dev/urandom是非阻塞的,在熵值不够时会产生伪随机数。
- /dev/random:随机性高,阻塞,真随机
- /dev/urandom:随机性稍差,非阻塞,可能产生伪随机
因此当有强随机性要求(密码,安全要求)时需要使用/dev/random。
使用SecureRandom一定获取到安全随机数吗?
SecureRandom使用的到底是/dev/random
还是/dev/urandom
呢?
查看SecureRandom
源码
public SecureRandom() {
/*
* This call to our superclass constructor will result in a call
* to our own {@code setSeed} method, which will return
* immediately when it is passed zero.
*/
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或rngd服务给linux补熵,这里以haveged为例。
安装haveged
yum install haveged
启动命令
systemctl start haveged.service
执行以下命令,开启服务开机自启动。
systemctl enable haveged.service
执行以下命令,查看是否开机启动。
systemctl is-enabled haveged.service
解决方法二:使用伪随机临时规避
只有将securerandom.strongAlgorithms
改为非阻塞的算法才能规避此问题,但是也将系统由安全随机改为伪随机,不能用于生产环境
securerandom.strongAlgorithms=NativePRNGNonBlocking:SUN