如何处理速度缓慢的SecureRandom生成器?

188

如果你想在Java中使用加密强度的随机数,可以使用SecureRandom。不幸的是,SecureRandom可能非常慢。如果它在Linux上使用/dev/random,它可能会阻塞等待足够的熵积累。如何避免性能损失?

有人尝试过Uncommon Maths来解决这个问题吗?

有人能确认JDK 6中已经解决了这个性能问题吗?


这似乎与SecureRandom.generateSeed()的缓慢有关。有一个被拒绝的缺陷解释了缓慢的原因并提供了解决方法:JDK-6521844 : SecureRandom hangs on Linux Systems - AlikElzin-kilaka
请查看/dev/urandom(而不是/dev/random)...如果存在阻塞问题,请考虑仅从urandom获取随机数生成器种子。 - jcalfee314
1
与Windows相关: https://dev59.com/qVUM5IYBdhLWcg3wF9PZ#49322949 - patrikbeno
@AlikElzin-kilaka - JDK-6521844的链接已经不存在了... - undefined
17个回答

184

你可以在Linux上使用以下方式选择更快但略微不太安全的/dev/urandom:

-Djava.security.egd=file:/dev/urandom

然而,这在Java 5及以后的版本中不起作用 (Java Bug 6202721)。建议的解决方法是使用:

-Djava.security.egd=file:/dev/./urandom

(注意额外的/./


25
请注意Java Bug报告中的说法:“不是缺陷”。换句话说,即使默认选项是“/dev/urandom”,Sun也将其视为魔术字符串并仍然使用“/dev/random”,因此您必须进行伪装。何时是file: URL而不是file: URL?每当Sun决定不是时 :-( - Jim Garrison
6
刚刚花费了不少时间调查后,发现即使在 -Djava.security.egdjava.security 文件中的 securerandom.source 中设置了 file:/dev/urandom,仍然会在调用 SecureRandom.getSeed()(或 setSeed())时读取 /dev/random/。使用 file:/dev/./urandom 的解决方法完全不会读取 /dev/random(通过 strace 确认)。 - matt b
7
当使用现代的CSPRNG实现时,/dev/urandom并不比/dev/random不安全。具体信息请参考维基百科上关于FreeBSD的相关页面:http://en.wikipedia.org/wiki//dev/random#FreeBSD - lapo
我认为/dev/urandom/的主要担忧是,如果您在新硬件上使用它来生成密钥,那么该硬件可能处于相当可预测的状态。即使这种情况下应该阻塞以获取熵,/dev/urandom/也不会阻塞。如果密钥是持久的,例如,如果设备在首次启动时的第一件事就是生成公私钥对,则情况甚至更糟。除了这些可怕的情况外,一个好的/dev/urandom比使用常见的SecureRandom算法更好。 - Steve Jessop
1
哪一个是正确的?-Djava.security.egd=file:/dev/./urandom 还是 file:///dev/urandom @mattb - Aarish Ramesh
显示剩余3条评论

95

如果你想要真正的随机数据,那么不幸的是你必须等待。这包括SecureRandom PRNG的种子。Uncommon Maths无法比SecureRandom更快地收集真正的随机数据,尽管它可以连接到互联网上从特定网站下载种子数据。我猜这不太可能比那些可用的/dev/random更快。

如果你想要一个PRNG,请按以下方式操作:

SecureRandom.getInstance("SHA1PRNG");

支持的字符串取决于SecureRandom SPI提供程序,但可以使用Security.getProviders()Provider.getService()来枚举它们。

Sun喜欢SHA1PRNG,因此它广泛可用。相对于其他PRNG,它的速度并不快,但PRNG只是在进行数字计算,而不是通过物理测量熵阻塞。

例外情况是,如果在获取数据之前不调用setSeed(),则PRNG将在第一次调用next()nextBytes()时自行初始化。通常情况下,它会使用系统中相当少量的真随机数据进行初始化。这个调用可能会阻塞,但它会使您的随机数源比任何变体的“将当前时间哈希在一起,加上27,然后祈求最好”的方法更安全。但是,如果您只需要用于游戏的随机数,或者如果您想要将流在未来使用相同的种子重复以进行测试目的,则不安全的种子仍然有用。


“it's widely available”. 它不是包含在每个兼容的JDK中吗?它在Java安全标准名称列表中... (https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecureRandom) - Sean Reilly
@Sean:我现在不记得这一切是如何工作的了(问题很久以前,我现在也不再使用Java)。但文档上说“由SUN提供”,所以它应该总是在JDK中,但不一定包含在除Oracle之外的其他Java实现中。 - Steve Jessop
来自标准名称文档的@SteveJessop:“JDK安全API需要并使用一组算法、证书和密钥库类型的标准名称。”提供程序名称在非Oracle JDK中肯定会有所不同,但我认为这要求算法名称是标准的且不会改变,即每个Java平台上始终会有SHA1PRNG的某些实现。 - Sean Reilly
@SeanReilly:幸运的是,我已经很多年没有运行TCK并与Sun争论失败是否意味着实现不符合规范,还是只是测试不匹配规范了 :-) - Steve Jessop
好的,我会稍微放松一下。使用应用程序特定的伪随机数生成器(PRNG),例如SHA1PRNG,可能会减轻系统的RNG负担。但是,由于它根据定义是不可移植的,因此您至少应该使算法名称可配置并在每个系统上进行测试。较新的Java版本也可能具有更好的NIST兼容PRNG。仅使用new SecureRandom()并配置提供程序顺序仍应优先考虑此方法。另请参见Dan的答案以获取替代PRNG。 - Maarten Bodewes
显示剩余11条评论

35
在Linux上,默认的SecureRandom实现是NativePRNG(源代码在此),这种方法通常非常慢。在Windows上,默认值是SHA1PRNG,正如其他人指出的那样,如果您明确指定,则也可以在Linux上使用它。 NativePRNG不同于SHA1PRNG和Uncommons Maths的AESCounterRNG,因为它会不断从操作系统接收熵(通过从/dev/urandom读取)。其他PRNG在初始化后不会获取任何额外的熵。
AESCounterRNG比SHA1PRNG快约10倍,而SHA1PRNG本身又比NativePRNG快两到三倍左右。
如果您需要一个更快的PRNG,可以尝试查找Fortuna的Java实现,它在初始化后会获取熵,核心PRNG与AESCounterRNG相同,但还有一个复杂的熵池系统和自动重新种子化。

这个链接无法打开。https://uncommons-maths.dev.java.net/nonav/api/org/uncommons/maths/random/AESCounterRNG.html。有其他地方可以查看吗? - UVM
@Unni 刚刚更新了链接。请注意,我在这个答案中提出的性能声明可能已经不再有效。我认为最近版本的Java可能有所改善,并且在平台之间(即Windows vs. Liux)可能存在性能差异。 - Dan Dyer
我只是在我的Windows 7电脑上运行了一个SecureRandom和MessageDigest的示例,并将其进行了十六进制编码。整个操作只花费了33毫秒。这是一个问题吗?我使用了SHA1PRNG。SecureRandom prng = SecureRandom.getInstance("SHA1PRNG"); String randomNum = new Integer(prng.nextInt()).toString(); MessageDigest sha = MessageDigest.getInstance("SHA-1"); result = sha.digest(randomNum.getBytes()); str = hexEncode(result); - UVM

31

许多Linux发行版(主要是基于Debian的)将OpenJDK配置为使用/dev/random来获取熵。

/dev/random按定义是慢的(甚至可能会阻塞)。

从这里开始,您有两个选择来解除阻塞:

  1. 增加熵,或者
  2. 减少随机性要求。

选项1,增加熵

为了向/dev/random中输入更多的熵,请尝试使用haveged守护程序。它是一个连续收集HAVEGE熵的守护程序,也适用于虚拟化环境,因为它不需要任何特殊硬件,只需要CPU本身和时钟。

在Ubuntu/Debian上:

apt-get install haveged
update-rc.d haveged defaults
service haveged start
在RHEL/CentOS上:
yum install haveged
systemctl enable haveged
systemctl start haveged

选项2. 减少随机性要求

如果由于某种原因上述解决方案无法帮助您或者您不关心密码强度随机性,您可以切换到/dev/urandom,它可以保证不会阻塞。

要在全局范围内执行此操作,请编辑位于默认Java安装中的文件jre/lib/security/java.security,以使用/dev/urandom(由于另一个bug需要指定为/dev/./urandom

如下所示:

#securerandom.source=file:/dev/random
securerandom.source=file:/dev/./urandom

那么你就永远不需要在命令行上指定它。


注意:如果你从事密码学,你需要具有良好的熵。以案例为例-安卓PRNG问题降低了比特币钱包的安全性。


1
我已经点赞了你的回答,但是“/dev/random是根据定义慢的(甚至可能会阻塞)”是错误的;这完全取决于系统配置。新机器可能在CPU中有一个快速的RNG可用,而BSD机器通常对/dev/random/devl/urandom采用相同的实现。尽管如此,您可能不应该指望/dev/random一定很快。在虚拟机上,您可能希望在客户端VM上安装客户端工具集,以便可以使用主机操作系统的RNG。 - Maarten Bodewes

18

我曾经遇到过与SecureRandom的调用在无人值守的Debian服务器上每次阻塞约25秒的类似问题。我安装了haveged守护程序来确保/dev/random被保持填充,对于无人值守的服务器,您需要像这样的东西来生成所需的熵。

现在我的SecureRandom调用可能只需几毫秒。


4
使用命令"apt-get install haveged"安装haveged,然后使用命令"update-rc.d haveged defaults"。 - Rod Lima

12
如果您需要真正"具有密码学强度"的随机性,则需要一个强熵源。由于系统事件需要等待熵收集(磁盘读取、网络数据包、鼠标移动、按键等),因此/dev/random速度较慢。
更快的解决方案是硬件随机数生成器。您的主板上可能已经内置了一个; 请查看hw_random documentation以了解如何确定是否拥有该功能以及如何使用它。rng-tools包包含一个daemon,将由硬件生成的熵馈入/dev/random
如果您系统上没有HRNG,并且您愿意为性能而牺牲熵强度,则需要使用/dev/random中的数据来初始化良好的PRNG,并让PRNG完成大部分工作。 SP800-90列出了几个NIST批准的PRNG,可以直接实现。

很好的观点,但我的代码是商业应用程序的一部分。我无法控制服务器环境。我认为目标服务器始终没有鼠标和键盘,并完全依赖磁盘和网络I/O来获取熵,这可能是根本问题。 - David G
3
我发现/dev/random依赖于系统事件,所以作为临时解决方案,我只是在我的测试运行时来回移动鼠标。 - David K
那个针对i820芯片组的82802集线器非常慢(RIP)。我很惊讶你能从中收集到任何有用的信息。我认为我在它上面花费的时间比收集八位字节还要多。 - jww

10
根据文档,SecureRandom使用的不同算法按优先顺序如下:
  • 在大多数*NIX系统(包括macOS)上
    1. PKCS11(仅适用于Solaris)
    2. NativePRNG
    3. DRBG
    4. SHA1PRNG
    5. NativePRNGBlocking
    6. NativePRNGNonBlocking
  • 在Windows系统上
    1. DRBG
    2. SHA1PRNG
    3. Windows-PRNG

由于您询问的是Linux,因此我将忽略Windows实现以及仅在Solaris上真正可用的PKCS11,除非您自己安装了它 - 如果您这样做了,您可能不会问这个问题。

根据相同的文档,这些算法使用的是:
NativePRNG

generateSeed() 使用 /dev/random
nextBytes() 使用 /dev/urandom

NativePRNGBlocking

generateSeed()nextBytes() 都使用 /dev/random

NativePRNGNonBlocking

generateSeed()nextBytes() 都使用 /dev/urandom

DRBG

支持的机制和算法:

  • 带有 SHA-224、SHA-512/224、SHA-256、SHA-512/256、SHA-384 和 SHA-512 的 Hash_DRBG 和 HMAC_DRBG。
  • 带有 AES-128、AES-192 和 AES-256 的 CTR_DRBG。
SHA1PRNG

初始种子目前是通过系统属性和java.security熵收集设备的组合完成的。


这意味着如果你使用 SecureRandom random = new SecureRandom(),它会按照列表顺序查找可用的算法,通常会选择 NativePRNG。这意味着它从 /dev/random 中获取种子(或者如果你明确生成了种子,则使用该种子),然后使用 /dev/urandom 获取下一个字节、整数、双精度浮点数、布尔值等。

由于 /dev/random 是阻塞的(直到熵池中有足够的熵),这可能会影响性能。

解决这个问题的一种方法是使用类似 haveged 的工具来生成足够的熵,另一种方法是改用 /dev/urandom。虽然你可以为整个 JVM 设置这个选项,但更好的解决方案是针对此特定的 SecureRandom 实例进行设置,即使用 SecureRandom random = SecureRandom.getInstance("NativePRNGNonBlocking")。请注意,如果 NativePRNGNonBlocking 不可用,该方法可能会抛出 NoSuchAlgorithmException 异常,因此要准备好回退到默认值。

SecureRandom random;
try {
    random = SecureRandom.getInstance("NativePRNGNonBlocking");
} catch (NoSuchAlgorithmException nsae) {
    random = new SecureRandom();
}

还要注意,在其他*nix系统上,{{link1:/dev/urandom的行为可能会有所不同}}。


/dev/urandom是否足够随机?

传统智慧认为只有/dev/random足够随机。然而,也有一些声音不同。在"正确使用SecureRandom的方法""关于/dev/urandom的神话"中,有人认为/dev/urandom/同样好。

信息安全堆栈上的用户同意这一观点。基本上,如果你需要问,/dev/urandom对你的目的来说是可以的。


1
感谢这个很棒的更新!“u”提供的主要区别就是不会因为熵而阻塞。在我们选择这里的英语单词时有一些有趣的方面:由于我们自己对随机事件的无知,它们在实际目的上都同样“安全”;阻塞以收集熵使其更随机,但并不更安全;如果你想用比掷骰子更好的熵来掷骰子,那么使用阻塞的方法,但如果你想处理银行交易,最好不要阻塞;任何一个的“伪”之处都只是传统用语。 - cregox

7
使用Java 8,在Linux上调用SecureRandom.getInstanceStrong()会给我提供NativePRNGBlocking算法。这经常会阻塞数秒钟以生成几个字节的盐。
我改为显式地请求NativePRNGNonBlocking,并且如预期的那样,它不再阻塞。我不知道这样做的安全影响是什么。可能非阻塞版本无法保证使用的熵量。
更新: 好吧,我找到了这篇优秀的解释
简而言之,要避免阻塞,请使用new SecureRandom()。这使用/dev/urandom,它不会阻塞,并且基本与/dev/random一样安全。从文章中得出: "您唯一想要调用 /dev/random 的时候是当机器首次启动且尚未累积熵值"。 SecureRandom.getInstanceStrong()为你提供绝对最强的随机数发生器,但只在不受堵塞影响的情况下使用才安全。

1
我只会允许getInstanceStrong()用于长期密钥,例如TLS证书密钥。即使如此,我也更愿意使用new SecureRandom()或符合FIPS标准的密钥对生成器或随机数生成器。所以是的,这提供了一个答案,如果 /dev/urandom没有阻塞:毕竟它仍然依赖于系统熵;但总的来说,这是非常好的建议。如果/dev/urandom被阻塞,您可能需要修复问题的源头而不是您的Java应用程序。 - Maarten Bodewes

6
您提到的关于/dev/random的问题并不在于SecureRandom算法,而在于它所使用的随机源。这两者是正交的。您应该确定其中哪一个正在拖慢您的速度。
您链接的Uncommon Maths页面明确指出他们没有涉及随机源。
您可以尝试不同的JCE提供程序,例如BouncyCastle,以查看其SecureRandom实现是否更快。
简短的搜索还显示了Linux补丁,用Fortuna替换默认实现。我不太了解这个,但欢迎您调查。
我还应该提到,虽然使用实现不良的SecureRandom算法和/或随机源非常危险,但您可以使用自定义的SecureRandomSpi实现来制作自己的JCE提供程序。您需要与Sun进行一些过程,以获得您的提供程序签名,但实际上非常简单;他们只需要您传真一份声明表格,说明您已经意识到加密库的美国出口限制。

只有当不同的JCE提供者使用另一种熵源时,它们才有用,这基本上意味着它们必须使用特定的硬件,例如HSM。否则,它们将同样可能出现减速,具体取决于它们从系统中提取多少熵。 - Maarten Bodewes

5

我遇到了相同的问题(issue)。经过一些使用正确搜索词汇进行谷歌搜索后,我发现了这篇关于DigitalOcean的好文章。

haveged是一种潜在的解决方案,不会影响安全性。

我仅引用了本文中相关的部分。

基于HAVEGE原理,并且之前基于其相关库,haveged允许生成基于处理器上代码执行时间变化的随机性。由于一个代码片段在同一环境和硬件上执行相同的时间几乎是不可能的,因此运行单个或多个程序的时间应适合用于种子随机源。haveged实现使用循环重复执行后CPU时间戳计数器(TSC)的差异来生成您系统的随机源(通常为/dev/random)

如何安装haveged

按照本文中的步骤进行操作。https://www.digitalocean.com/community/tutorials/how-to-setup-additional-entropy-for-cloud-servers-using-haveged

我在这里发布了它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接