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

如果需要在 Java 中使用加密强随机数,可以使用 SecureRandom。不幸的是,SecureRandom可能非常慢。如果它在 Linux 上使用 /dev/random,它可以阻止等待足够的熵积累。如何避免性能惩罚?

有人使用 罕见的数学作为这个问题的解决方案吗?

有人能证实 JDK 6已经解决了这个性能问题吗?

160660 次浏览

我自己还没有碰到过这个问题,但是我在程序启动时产生了一个线程,它会立即尝试生成一个种子,然后死亡。随机调用的方法如果是活的,就会加入到那个线程中,所以第一个调用只有在程序执行很早的时候才会阻塞。

您提到的关于 /dev/random的问题不是与 SecureRandom算法有关,而是与它所使用的随机性的来源有关。两者是正交的。你应该搞清楚是哪一个拖了你的后腿。

您链接的罕见数学页面明确提到,它们没有处理随机性的来源。

您可以尝试不同的 JCE 提供程序,比如 BouncyCastle,看看它们对 SecureRandom的实现是否更快。

简短的 搜索还揭示了用 Fortune 替换默认实现的 Linux 补丁。我对此知之甚少,但欢迎你来调查。

我还应该提到,虽然使用实现不好的 SecureRandom算法和/或随机性源是非常危险的,但是您可以使用自定义的 SecureRandomSpi实现来运行自己的 JCE Provider。您需要与 Sun 一起完成一个过程才能让您的提供者签名,但是这实际上非常简单; 他们只需要您向他们传真一份表格,说明您知道美国对加密库的出口限制。

如果你想要真正的随机数据,那么不幸的是,你必须等待它。这包括 SecureRandom PRNG 的种子。罕见的数学不能收集真正的随机数据任何快于 SecureRandom,虽然它可以连接到互联网下载种子数据从一个特定的网站。我的猜测是,这不太可能比 /dev/random更快,在那里可用。

如果你想要一个 PRNG,可以这样做:

SecureRandom.getInstance("SHA1PRNG");

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

Sun 很喜欢 SHA1PRNG,所以它被广泛使用。它不是特别快,因为 PRNG 去,但 PRNG 将只是处理数字,而不是阻塞物理测量熵。

例外的情况是,如果在获取数据之前不调用 setSeed(),那么在第一次调用 next()nextBytes()时,PRNG 将自行播种。它通常使用来自系统的相当少量的真实随机数据来完成这项工作。这个调用可能会阻塞,但是它会使您的随机数源比任何变体“将当前时间与 PID 一起散列,加上27,并希望最好”更加安全。但是,如果您所需要的只是游戏中的随机数,或者如果您希望将来使用相同的种子进行测试时流是可重复的,那么不安全的种子仍然是有用的。

我的经验只是在 PRNG 的缓慢初始化阶段,而不是在那之后生成随机数据。尝试一种更积极的初始化策略。因为它们的创建成本很高,所以应该像对待单例一样对待它,并重用相同的实例。如果对于一个实例有太多的线程争用,那么将它们池起来或者使它们成为线程本地的。

不要在随机数生成上妥协。那里的一个弱点会危及你所有的安全。

我没有看到很多基于 COTS 原子衰变的生成器,但是如果你真的需要很多随机数据,有几个计划可以用来生成它们。有一个网站总是有很多有趣的东西值得一看,包括 HotBits,那就是 John Walker 的 Fourmilab。

在 Linux 上,SecureRandom的默认实现是 NativePRNG(源代码 给你) ,它往往非常慢。在 Windows 上,默认值是 SHA1PRNG,正如其他人指出的那样,如果显式指定了 SHA1PRNG,也可以在 Linux 上使用它。

NativePRNG不同于 SHA1PRNG和 Uncommons Maths 的 AESCounterRNG,因为它不断地从操作系统接收熵(从 /dev/urandom读取)。其他 PRNGs 在播种后不会获得任何额外的熵。

AESCounterRNG 比 SHA1PRNG快10倍,而 SHA1PRNG本身的 IIRC 比 NativePRNG快2到3倍。

如果需要在初始化后获得熵的更快的 PRNG,请查看是否可以找到 福图纳的 Java 实现。Fortune 实现的核心 PRNG 与 AESCounterRNG 相同,但是也有一个复杂的熵池和自动重新播种系统。

使用安全随机作为一个循环算法的初始化源,然后你可以使用一个梅森旋转算法来代替 UncommonMath 中的一个,它已经存在了一段时间并且被证明比其他 prng 更好

Http://en.wikipedia.org/wiki/mersenne_twister

确保不时刷新用于初始化的安全随机,例如,你可以为每个客户端生成一个安全随机,每个客户端使用一个梅森旋转算法伪随机生成器,获得足够高的随机化程度

如果你想要真正的“加密强”随机性,那么你需要一个强熵源。/dev/random的速度很慢,因为它必须等待系统事件收集熵(磁盘读取、网络数据包、鼠标移动、按键等)。

一个更快的解决方案是硬件随机数发生器。您可能已经有一个内置到您的主板; 查看 随机文档的说明,了解如果您有它,以及如何使用它。Rng-tools 包包括一个守护进程,它将向 /dev/random提供硬件生成的熵。

如果您的系统上没有 HRNG,并且您愿意为了性能而牺牲熵强度,那么您将希望使用来自 /dev/random的数据生成一个好的 PRNG,并让 PRNG 完成大部分工作。在 SP800-90中列出了几个 NIST 批准的 PRNG,它们直接实现。

另一个值得关注的是文件 lib/security/java.security 中的 securerRandom. source 属性

使用/dev/urandom 而不是/dev/Random 可能会带来性能方面的好处。记住,如果随机数的质量很重要,不要做出破坏安全性的妥协。

听起来你应该更清楚你的 RNG 需求。最强大的加密 RNG 要求(据我所知)是,即使你知道用于生成它们的算法,并且你知道以前生成的所有随机数,如果不花费不切实际的计算能力,你不可能得到任何关于未来生成的随机数的有用信息。

如果您不需要这种对随机性的完全保证,那么可能存在适当的性能折衷。我倾向于同意 丹 · 戴尔的回应关于 Uncommons-Maths 的 aesCounterrNG 或 Fortune (其作者之一是密码学专家布鲁斯•施奈尔(bruceschneier))的观点。我也从来没有用过,但是这些想法乍一看还是很有名的。

如果你可以周期性地生成一个初始随机种子(例如每天或每小时一次) ,你可以使用快速流密码从流的连续块中生成随机数(如果流密码使用 XOR,那么只需要传入一个空值流或直接获取 XOR 位)。ECRYPT 的 EStream项目有许多好的信息,包括性能基准。这样就不会在你补充它的时间点之间保持熵,所以如果有人知道一个随机数和你使用的算法,技术上来说,有可能在计算能力很强的情况下,破解流密码并猜测其内部状态,从而能够预测未来的随机数。但你必须决定,这种风险及其后果是否足以证明维持熵的成本是合理的。

编辑: 这是我在网上找到的一些 密码学课程笔记,看起来和这个主题非常相关。

您应该能够使用以下方法在 Linux 上选择更快但安全性稍差的/dev/urandom:

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

然而,这并不适用于 Java5及更高版本(Java Bug 6202721):

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

(注意额外的 /./)

有一个工具(至少在 Ubuntu 上)可以将人为的随机性输入到你的系统中。命令很简单:

rngd -r /dev/urandom

你可能需要一个 sudo 在前面。如果没有 rng-tools 包,则需要安装它。我试过了,绝对有用!

资料来源: Matt VS 世界

如果您的硬件支持它尝试 使用 Java RdRand 实用程序,其中我是作者。

它基于英特尔的 RDRAND指令,比 SecureRandom快10倍左右,对于大容量实现没有带宽问题。


注意,这个实现只能在那些提供指令的 CPU 上运行(例如,当设置了 rdrand处理器标志时)。您需要通过 RdRandRandom()构造函数显式地实例化它; 没有实现特定的 Provider

我也遇到过类似的问题,在一个没有头的 Debian 服务器上,对 SecureRandom的调用每次被阻塞大约25秒。我安装的 haveged守护进程,以确保 /dev/random是不断补充,在无头服务器上,您需要这样的东西,以产生所需的熵。 我现在打给 SecureRandom的电话可能需要几毫秒。

我遇到了同样的 问题。经过一些正确的搜索关键词谷歌,我遇到了这个很好的文章在 数字海洋

是一个潜在的解决方案,不妥协的安全。

我只是引用这篇文章的相关部分。

基于 HAVEGE 原理,并以前基于其相关 图书馆,已经允许产生随机性的基础上的变化 代码在处理器上的执行时间 执行一段代码所需的精确时间相同,即使在 同一个环境在同一个硬件上,运行时间单 或多个程序应该适合于种子一个随机来源 有限的实现为系统的随机源埋下了种子(通常是 使用处理器的时间戳计数器中的差异 (TSC)在重复执行一个循环之后

如何安装

按照本文中的步骤操作

我已经发布了它 给你

使用 Java8,我发现在 Linux 上调用 SecureRandom.getInstanceStrong()会得到 NativePRNGBlocking算法。这通常会阻塞许多秒以生成几个字节的 salt。

我切换到显式请求 NativePRNGNonBlocking,正如名称所预期的那样,它不再被阻塞。我不知道这对安全有什么影响。据推测,非阻塞版本不能保证使用熵的数量。

更新 : 好的,我找到了 这个绝妙的解释

简而言之,为了避免阻塞,使用 new SecureRandom()。这使用了 /dev/urandom,它不会阻塞,基本上和 /dev/random一样安全。帖子中写道: “您唯一想要调用/dev/Random 的时间是机器第一次引导时,熵还没有积累起来的时候”。

SecureRandom.getInstanceStrong()给你提供了绝对最强的 RNG,但它只有在一堆阻塞不会影响你的情况下才是安全的。

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

根据定义,/dev/random很慢(甚至可以阻塞)。

从现在开始,你有两个选择来解锁它:

  1. 提高熵,或者
  2. 降低随机性要求。

选项一,提高熵

要在 /dev/random中获得更多的熵,请尝试 守护进程。它是一个不断收集 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(由于另一个 臭虫需要将其指定为 /dev/./urandom)。

像这样:

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

这样您就不必在命令行中指定它了。


注意: 如果你做密码学,你的 需要好熵。例如,机器人 PRNG 问题降低了比特币钱包的安全性。

根据 文件,SecureRandom 使用的不同算法按优先顺序如下:

  • 在大多数 * NIX 系统(包括 macOS)上
    1. PKCS11(仅在 Solaris 上)
    2. NativePRNG
    3. Sha1PRNG
    4. 原生 PRNGBlock
    5. 非阻塞
  • 在 Windows 系统上
    1. DRBG
    2. Sha1PRNG
    3. 视窗 -PRNG

既然你问到 Linux,我就忽略 Windows 的实现,以及只在 Solaris 上真正可用的 PKCS11,除非你自己安装它ーー如果你自己安装了,你可能就不会问这个问题了。

根据同样的文档,这些算法使用的是什么

Sha1PRNG

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

NativePRNG

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

原生 PRNGBlock

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

非阻塞

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


这意味着如果您使用 SecureRandom random = new SecureRandom(),它将沿着这个列表向下,直到找到一个可以工作的,通常是 NativePRNG。这意味着它从 /dev/random开始生成种子(或者在显式生成种子时使用它) ,然后使用 /dev/urandom获取下一个字节、 int、 double、 boolean 和 what-have-you。

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

解决这个问题的一个办法是使用诸如 haveged 之类的东西来产生足够的熵,另一个办法是使用 /dev/urandom代替。虽然可以为整个 jvm 设置这个值,但是更好的解决方案是使用 SecureRandom random = SecureRandom.getInstance("NativePRNGNonBlocking")为这个特定的 SecureRandom实例设置这个值。注意,如果 NativePRNGNonBlock 不可用,那么该方法可能会抛出 NoSuchAlobacmException,因此请准备回退到默认值。

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

还要注意,在其他 * nix 系统上,/dev/urandom的表现可能不同


/dev/urandom是否足够随机?

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

信息安全栈 我同意上的用户。基本上,如果你不得不问,/dev/urandom对于你的目的是好的。