java.security.egd JVM选项

2023/07/02

1. 概述

启动Java虚拟机(JVM)时,我们可以定义各种属性来改变JVM的行为方式,其中一个属性是java.security.egd。

在本教程中,我们将研究它是什么、如何使用它以及它有什么作用。

2. java.security.egd是什么?

作为JVM属性,我们可以使用java.security.egd来影响SecureRandom类的初始化方式。

与所有JVM属性一样,我们在启动JVM时在命令行中使用-D参数声明它:

java -Djava.security.egd=file:/dev/urandom -cp . cn.tuyucheng.taketoday.java.security.JavaSecurityEgdTester

通常,如果我们在Linux上运行Java 8或更高版本,那么我们的JVM将默认使用file:/dev/urandom。

3. java.security.egd有什么作用?

当我们第一次调用从SecureRandom读取字节时,我们会使其初始化并读取JVM的java.security配置文件。此文件包含一个securerandom.source属性

securerandom.source=file:/dev/random

安全提供程序(比如默认的sun.security.provider.Sun)在初始化的时候会读取这个属性

当我们设置java.security.egd JVM属性时,安全提供程序可能会使用它来覆盖在securerandom.source中配置的那个。

当我们使用SecureRandom生成随机数时,java.security.egd和securerandom.source控制哪个熵收集设备(EGD)将被用作种子数据的主要来源。

在Java 8之前,我们可以在$JAVA_HOME/jre/lib/security中找到java.security,但在以后的实现中,它在$JAVA_HOME/conf/security中。

egd选项是否有效取决于安全提供程序的实现

4. java.security.egd可以取什么值?

我们可以用URL格式指定java.security.egd,其值如下:

  • file:/dev/random
  • file:/dev/urandom
  • file:/dev/./urandom

此设置是否有任何效果,或任何其他值是否有所不同,取决于我们使用的平台和Java版本,以及我们的JVM安全性是如何配置的

在基于Unix的操作系统(OS)上,/dev/random是一个特殊的文件路径,它作为普通文件出现在文件系统中,但从中读取实际上与操作系统的设备驱动程序交互以生成随机数。一些设备实现还提供通过/dev/urandom甚至/dev/arandom URI的访问。

5. file:/dev/./urandom有什么特别之处?

首先,让我们了解文件/dev/random和/dev/urandom之间的区别:

  • /dev/random从各种来源收集熵;/dev/random将阻塞,直到它有足够的熵来满足我们对不可预测数据的读取请求
  • /dev/urandom将从可用的任何内容中获取伪随机性,而不会阻塞

当我们第一次使用SecureRandom时,我们的默认Sun SeedGenerator会初始化。

当我们使用特殊值file:/dev/random或file:/dev/urandom时,我们会导致Sun SeedGenerator使用本机(平台)实现。

Unix上的提供程序实现可能会因仍然从/dev/random读取而阻塞。在Java 1.4中,一些实现被发现有这个问题,该错误随后在Java 8的JDK增强提案(JEP123)下得到修复。

使用file:/dev/./urandom等URL或任何其他值,会使SeedGenerator将其视为我们要使用的种子源的URL

在类Unix系统上,我们的file:/dev/./urandom URL解析为相同的非阻塞/dev/urandom文件。

但是,我们并不总是希望使用这个值。在Windows上,我们没有此文件,因此我们的URL无法解析。这会触发生成随机性的最终机制,并可能将我们的初始化延迟约5秒。

6. SecureRandom的演变

java.security.egd的作用在不同的Java版本中发生了变化。

那么,让我们看看影响SecureRandom行为的一些更重要的事件:

了解SecureRandom的变化方式让我们深入了解java.security.egd属性的可能影响。

7. 测试java.security.egd的效果

确定JVM属性效果的最佳方法是尝试它。因此,让我们通过运行一些代码来创建新的SecureRandom并计算获取一些随机字节所需的时间来查看java.security.egd的效果。

首先,让我们创建一个带有main()方法的JavaSecurityEgdTester类,我们将使用System.nanoTime()对secureRandom.nextBytes()的调用计时并显示结果:

public class JavaSecurityEgdTester {
    public static final double NANOSECS = 1000000000.0;

    public static void main(String[] args) {
        SecureRandom secureRandom = new SecureRandom();
        long start = System.nanoTime();
        byte[] randomBytes = new byte[256];
        secureRandom.nextBytes(randomBytes);
        double duration = (System.nanoTime() - start) / NANOSECS;

        System.out.println("java.security.egd = " + System.getProperty("java.security.egd") + " took " + duration + " seconds and used the " + secureRandom.getAlgorithm() + " algorithm");
    }
}

现在,让我们通过启动一个新的Java实例并为java.security.egd属性指定一个值来运行JavaSecurityEgdTester测试:

java -Djava.security.egd=file:/dev/random -cp . cn.tuyucheng.taketoday.java.security.JavaSecurityEgdTester

让我们检查输出,看看我们的测试花了多长时间以及使用了哪种算法:

java.security.egd=file:/dev/random took 0.692 seconds and used the SHA1PRNG algorithm

由于我们的系统属性只在初始化时读取,因此让我们在新的JVM中为java.security.egd的每个不同值启动我们的类:

java -Djava.security.egd=file:/dev/urandom -cp . cn.tuyucheng.taketoday.java.security.JavaSecurityEgdTester
java -Djava.security.egd=file:/dev/./urandom -cp . cn.tuyucheng.taketoday.java.security.JavaSecurityEgdTester
java -Djava.security.egd=tuyucheng -cp . cn.tuyucheng.taketoday.java.security.JavaSecurityEgdTester

在使用Java 8或Java 11的Windows上,使用值file:/dev/random或file:/dev/urandom进行的测试给出亚秒级时间。使用其他任何东西,例如file:/dev/./urandom,甚至tuyucheng,都会使我们的测试花费超过5秒

有关为什么会发生这种情况的解释,请参阅我们之前的部分。在Linux上,我们可能会得到不同的结果。

8. SecureRandom.getInstanceStrong()怎么样?

Java 8引入了SecureRandom.getInstanceStrong()方法。让我们看看这如何影响我们的结果。

首先,让我们将new SecureRandom()替换为SecureRandom.getInstanceStrong():

SecureRandom secureRandom = SecureRandom.getInstanceStrong();

现在,让我们再次运行测试:

java -Djava.security.egd=file:/dev/random -cp . cn.tuyucheng.taketoday.java.security.JavaSecurityEgdTester

在Windows上运行时,java.security.egd属性的值在使用SecureRandom.getInstanceStrong()时没有明显的影响,即使是无法识别的值也会给我们快速响应。

让我们再次检查我们的输出并注意不到0.01秒的时间,我们还可以观察到该算法现在是Windows-PRNG:

java.security.egd=tuyucheng took 0.003 seconds and used the Windows-PRNG algorithm

请注意,算法名称中的PRNG代表伪随机数生成器。

9. 播种算法

由于随机数在密码学中大量用于安全密钥,因此它们必须是不可预测的。

因此,我们如何播种算法直接影响它们产生的随机数的可预测性。

为了产生不可预测性,SecureRandom实现使用从累积输入中收集的熵来播种他们的算法。这来自于鼠标和键盘等IO设备。

在类Unix系统上,我们的熵累积在文件/dev/random中

Windows上没有/dev/random文件,将-Djava.security.egd设置为file:/dev/random或file:/dev/urandom会导致默认算法(SHA1PRNG)使用本机Microsoft Crypto API进行播种

10. 虚拟机呢?

有时,我们的应用程序可能在/dev/random中很少或没有熵收集的虚拟机中运行。

虚拟机没有物理鼠标或键盘来生成数据,因此/dev/random中的熵积累要慢得多。这可能会导致我们的默认SecureRandom调用阻塞,直到有足够的熵来生成不可预测的数字。

我们可以采取一些措施来缓解这种情况。例如,在RedHat Linux中运行VM时,系统管理员可以配置虚拟IO随机数生成器virtio-rng,这会从托管它的物理机器中读取熵。

11. 故障排除技巧

如果我们的应用程序在它或其依赖项生成SecureRandom数字时挂起,请考虑java.security.egd-特别是当我们在Linux上运行时,以及如果我们在Java 8之前的版本上运行。

我们的Spring Boot应用程序经常使用嵌入式Tomcat,这使用SecureRandom生成会话密钥。当我们看到Tomcat的“Creation of SecureRandom instance”操作需要5秒或更长时间时,我们应该为java.security.egd尝试不同的值

12. 总结

在本教程中,我们了解了JVM属性java.security.egd是什么、如何使用它以及它有什么作用。我们还发现它的效果会根据我们运行的平台和我们使用的Java版本而有所不同。

最后一点,我们可以在JCA参考指南SecureRandom API规范的SecureRandom部分阅读更多关于SecureRandom及其工作原理的信息,并了解一些关于urandom的神话

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章