【问题标题】:How to deal with a slow SecureRandom generator?如何处理缓慢的 SecureRandom 生成器?
【发布时间】:2010-09-13 07:49:49
【问题描述】:

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

有没有人用Uncommon Maths 来解决这个问题?

谁能确认这个性能问题已经在 J​​DK 6 中解决了?

【问题讨论】:

标签: java performance security random entropy


【解决方案1】:

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

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

但是,这不适用于 Java 5 及更高版本 (Java Bug 6202721)。建议的解决方法是使用:

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

(注意额外的/./

【讨论】:

  • 请注意,Java 错误报告显示“不是缺陷”。换句话说,即使默认值为/dev/urandom,Sun 仍将其视为魔术字符串并使用/dev/random,因此您必须伪造它。 file: URL 何时不是 file: URL?每当 Sun 决定不是 :-(
  • 刚刚花了很多时间调查这个,似乎正常设置,即使在-Djava.security.egdsecurerandom.source中设置file:/dev/urandom在java.security文件中,/dev/random/每当SecureRandom.getSeed()(或setSeed() 被调用)时仍会被读取。 file:/dev/./urandom 的解决方法导致根本不读取 /dev/random(通过 strace 确认)
  • /dev/urandom 在使用现代 CSPRNG 实现时并不比 /dev/random 安全:en.wikipedia.org/wiki//dev/random#FreeBSD
  • 我认为/dev/urandom/ 的主要恐惧是如果您使用它在开箱即用的新硬件上生成秘密会发生什么,这可能处于完全可预测的状态。 /dev/urandom/ 不会阻止熵,即使这是你应该这样做的一种情况。如果秘密是持久的,情况会更糟,例如,如果您的设备在第一次启动时做的第一件事就是生成一个公钥-私钥对。除了那些可怕的情况之外,一个好的/dev/urandom 总比使用常见的SecureRandom 算法要好。
  • 哪一个是正确的? -Djava.security.egd=file:/dev/./urandom 或 file:///dev/urandom @mattb
【解决方案2】:

如果您想要真正的随机数据,那么不幸的是您必须等待它。这包括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() 时自行播种。它通常会使用来自系统的少量真实随机数据来执行此操作。此调用可能会阻塞,但会使您的随机数来源比“将当前时间与 PID 一起散列,加 27,并希望最好”的任何变体更加安全。但是,如果您只需要游戏的随机数,或者如果您希望流在未来可重复使用相同的种子进行测试,那么不安全的种子仍然有用。

【讨论】:

  • Uncommons Maths 仅从 Internet 下载数据用于播种,生成随机数时不会返回该随机数据。
  • 与 SecureRandom 相同 - /dev/urandom 仅用于播种。
  • 是的。当提问者说“如果你想要一个随机数,你可以使用 SecureRandom - 这可能会很慢”,我想也许他正在使用 getSeed 来处理所有事情并耗尽他的熵池。解决方法不是获取 JDK 6,而是按照预期的方式使用 SecureRandom ;-)
  • @Dan Dyer - 我已经更正了我对 Uncommon Maths 的评论。我确实看过你的页面,所以我知道“随机数”是指“为了它的种子”而不是“返回给用户”。但你说的很对,这不是我说的......
  • “它被广泛使用”。它不是包含在 every 兼容的 JDK 中吗?它在 java 安全标准名称列表中... (docs.oracle.com/javase/8/docs/technotes/guides/security/…)
【解决方案3】:

在 Linux 上,SecureRandom 的默认实现是NativePRNG(源代码here),这往往很慢。在 Windows 上,默认值为 SHA1PRNG,正如其他人指出的那样,如果您明确指定它,您也可以在 Linux 上使用它。

NativePRNGSHA1PRNG 和Uncommons Maths 的AESCounterRNG 不同之处在于它不断地从操作系统接收熵(通过从/dev/urandom 读取)。其他 PRNG 在播种后不会获得任何额外的熵。

AESCounterRNG 比 SHA1PRNG 快大约 10 倍,而 IIRC 本身比 NativePRNG 快两到三倍。

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

【讨论】:

  • 此链接无效。uncommons-maths.dev.java.net/nonav/api/org/uncommons/maths/…。有什么地方可以看到这个吗?
  • @Unni 刚刚更新了链接。请注意,我在此答案中所做的性能声明可能不再有效。我认为 Java 的最新版本可能会有所好转,并且平台之间的性能可能存在差异(即 Windows 与 Liux)。
  • 我只是在运行一个带有 MessageDigest 的 SecureRandom 示例并对其进行了十六进制编码。我的 windows 7 PC 中的整个操作需要 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(结果);
【解决方案4】:

许多 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

那么您就不必在命令行中指定它了。


注意:如果您从事密码学,您需要良好的熵。举个例子——android PRNG issue 降低了比特币钱包的安全性。

【讨论】:

  • 赞成您的回答,但“/dev/random 的定义很慢(甚至可以阻止)”是错误的;这完全取决于系统配置。较新的机器可能有例如CPU 中可以使用的快速 RNG,BSD 机器通常对/dev/random/devl/urandom 具有相同的实现。不过,您可能不应该依赖 /dev/random 一定要快。在 VM 上,您可能希望在客户端 VM 上安装客户端工具集,以便可以使用主机操作系统的 RNG。
【解决方案5】:

我在调用SecureRandom 时遇到了类似的问题,在无头 Debian 服务器上一次阻塞大约 25 秒。我安装了haveged 守护进程以确保/dev/random 保持充值,在无头服务器上,您需要这样的东西来生成所需的熵。 我对SecureRandom 的呼叫现在可能需要几毫秒。

【讨论】:

  • apt-get install haveged then update-rc.d haveged defaults
【解决方案6】:

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

更快的解决方案是硬件随机数生成器。您的主板可能已经内置了一个;查看hw_random documentation 了解有关确定您是否拥有它以及如何使用它的说明。 rng-tools 包包含一个守护进程,它将硬件生成的熵提供给/dev/random

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

【讨论】:

  • 好点,但我的代码是商业应用程序的一部分。我无法控制服务器环境。我认为目标服务器总是没有鼠标和键盘,完全依赖磁盘和网络 I/O 来获取熵,这可能是根本问题。
  • 我发现 /dev/random 依赖于系统事件,因此作为临时解决方法,我只是在测试运行时来回移动鼠标......
  • i820 芯片组的 82802 集线器速度非常慢 (RIP)。我很惊讶你能从中收集到任何有用的东西。我想我花了更多时间阻止它而不是收集八位字节。
【解决方案7】:

根据the documentation,SecureRandom 使用的不同算法按优先顺序排列:

  • 在大多数 *NIX 系统(包括 macOS)上
    1. PKCS11(仅在 Solaris 上)
    2. NativePRNG
    3. SHA1PRNG
    4. NativePRNG 阻塞
    5. NativePRNGNonBlocking
  • 在 Windows 系统上
    1. DRBG
    2. SHA1PRNG
    3. Windows-PRNG

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

根据相同的文档,what these algorithms use

SHA1PRNG

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

NativePRNG

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

NativePRNGBlocking

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

NativePRNGNonBlocking

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


这意味着如果您使用SecureRandom random = new SecureRandom(),它会在该列表中查找,直到找到一个有效的,通常是 NativePRNG。这意味着它从/dev/random 为自己播种(或者如果您显式生成种子,则使用它),然后使用/dev/urandom 获取下一个字节、整数、双精度、布尔值、what-have-yous。

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

一个解决方案是使用类似 hasged 的​​东西来生成足够的熵,另一个解决方案是使用 /dev/urandom 代替。虽然您可以为整个 jvm 设置它,但更好的解决方案是使用SecureRandom random = SecureRandom.getInstance("NativePRNGNonBlocking")SecureRandom 的这个特定实例进行设置。请注意,如果 NativePRNGNonBlocking 不可用,该方法可能会抛出 NoSuchAlgorithmException,因此请准备好回退到默认值。

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

另请注意,在其他 *nix 系统上,/dev/urandom may behave differently


/dev/urandom 足够随机吗?

传统观点认为只有/dev/random 是足够随机的。但是,有些声音不同。在"The Right Way to Use SecureRandom""Myths about /dev/urandom" 中,有人认为/dev/urandom/ 一样好。

信息安全堆栈上的用户agree with that。基本上,如果你不得不问,/dev/urandom 很适合你的目的。

【讨论】:

  • 感谢您的精彩更新! “u”提供的主要区别只是不会因为熵而阻塞。我们集体为这里的想法选择的英语单词有一些有趣的方面:​​它们在所有实际用途中都同样“安全”,因为我们自己对随机甚至意味着什么一无所知;阻止收集熵使其更加随机,根本不安全;如果你想掷一个比掷实际骰子熵更好的骰子,请使用阻塞式,如果你想处理银行交易,最好不要阻塞它;两者中唯一的“伪”是遗留的措辞。
【解决方案8】:

使用 Java 8,我发现在 Linux 上调用 SecureRandom.getInstanceStrong() 会给我NativePRNGBlocking 算法。这通常会阻塞几秒钟以生成几个字节的盐。

我改为明确要求NativePRNGNonBlocking,正如名称所预期的那样,它不再被阻止。我不知道这对安全有什么影响。想必非阻塞版本不能保证使用的熵量。

更新:好的,我找到了this excellent explanation

简而言之,为避免阻塞,请使用new SecureRandom()。这使用/dev/urandom,它不会阻塞并且基本上与/dev/random 一样安全。来自帖子:“您唯一想调用 /dev/random 的时间是机器首次启动时,熵尚未累积”。

SecureRandom.getInstanceStrong() 为您提供绝对最强的 RNG,但只有在一堆阻塞不会影响您的情况下才能安全使用。

【讨论】:

  • 我只会 allow getInstanceStrong() 用于长期密钥,例如用于 TLS 证书的密钥。即便如此,我宁愿使用new SecureRandom() 或符合 FIPS 的密钥对生成器或随机数生成器。所以是的,这提供了一个答案,if /dev/urandom 不会阻塞:最终它仍然依赖于系统熵;但这是非常好的建议总的来说。如果 /dev/urandom 阻塞,您可能需要修复问题的根源,而不是您的 Java 应用程序。
【解决方案9】:

您提到的关于/dev/random 的问题不在于SecureRandom 算法,而在于它使用的随机性来源。两者是正交的。您应该弄清楚两者中的哪一个在减慢您的速度。

您链接的罕见数学页面明确提到他们没有解决随机性的来源。

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

简短的search 还揭示了用 Fortuna 替换默认实现的 Linux 补丁。我对此了解不多,但欢迎您调查。

我还应该提一下,虽然使用实现不佳的SecureRandom 算法和/或随机源非常危险,但您可以使用SecureRandomSpi 的自定义实现来滚动您自己的 JCE 提供程序。您需要通过 Sun 的流程来签署您的提供商,但这实际上非常简单;他们只需要您向他们传真一份表格,说明您了解美国对加密库的出口限制。

【讨论】:

  • 那些不同的 JCE 提供者只有在使用另一个熵源时才有用,这基本上意味着他们必须使用特定的硬件,例如 HSM。否则,它们同样可能会遇到减速,这取决于它们从系统中提取了多少熵。
【解决方案10】:

有一个工具(至少在 Ubuntu 上)可以为您的系统提供人工随机性。命令很简单:

rngd -r /dev/urandom

您可能需要在前面添加一个 sudo。如果您没有 rng-tools 软件包,则需要安装它。我试过了,它确实对我有帮助!

来源:matt vs world

【讨论】:

  • 这有点危险,因为它在系统范围内完全禁用了 Linux 内核的熵水平估计。我认为出于测试目的(阅读:Jenkins 运行应用程序的测试套件)使用 /dev/./urandom 很好,但在生产中,它不是。
  • 这实际上是唯一对我有用的解决方案。在 Jenkins CI 上使用 Gradle 构建 Android 项目时,我遇到了“熵不足”的问题,并且将参数传递给构建没有帮助。
  • 我不得不在 xenial 中将 sudo rngd -r /dev/urandomsudo apt install rng-tools 结合起来
【解决方案11】:

我遇到了同样的issue。在使用正确的搜索词进行谷歌搜索后,我在DigitalOcean 上看到了这篇不错的文章。

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

我只是在这里引用文章中的相关部分。

基于 HAVEGE 原则,之前基于其相关的 图书馆,haveged 允许根据变化产生随机性 处理器上的代码执行时间。因为这几乎是不可能的 一段代码花费相同的确切时间来执行,即使在 同环境同硬件,单机运行时序 或多个程序应该适合播种随机源。这 haveged 实现为您系统的随机源提供种子(通常 /dev/random) 使用处理器时间戳计数器的差异 (TSC) 重复执行循环后

如何安装haveged

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

我已经发布了here

【讨论】:

    【解决方案12】:

    我自己没有遇到过这个问题,但是我会在程序启动时产生一个线程,它会立即尝试生成种子,然后死掉。如果您调用 randoms 的方法,它将加入该线程,如果它是活动的,那么第一个调用只有在程序执行的早期发生时才会阻塞。

    【讨论】:

    • 这是一个相当极端的 hack,但它可能会起作用;并不是说用过的 PRNG 可能不会使用额外的种子材料,这仍然会导致阻塞。应该强烈推荐使用不同的随机数来提供或固定系统中的熵。由于它至少可以提供一个临时解决方案,因此我仍然对答案投了赞成票。
    【解决方案13】:

    我的经验只是 PRNG 的缓慢初始化,而不是之后随机数据的生成。尝试更急切的初始化策略。由于创建它们的成本很高,因此将其视为单例并重用相同的实例。如果一个实例的线程争用过多,请将它们池化或使它们成为线程本地的。

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

    我没有看到很多基于 COTS 原子衰变的生成器,但如果您真的需要大量随机数据,则有一些针对它们的计划。 John Walker's Fourmilab.

    是一个总是有有趣的东西值得一看的网站,包括 HotBits

    【讨论】:

    • 我一直想知道这一点,因为强子 tau 衰变产品几乎达到了随机来源的理想状态,所以我无法摆脱使用它而不是算法工具的愿望。出于 op 的目的,我很久以前就决定,一些前端时间是所有安全工具所特有的。如果需要一个随机化器,它可以在构造函数中调用,并且只记得在页面加载时构造一个,它被隐藏在 avl 交换下,即使像我一样挑剔它也不会被注意到。跨度>
    • 英特尔 8xx 芯片组(可能还有许多其他芯片组)具有使用热噪声的硬件 RNG,这是一种真正不可预测的量子效应。可信平台模块也可以包含硬件 RNG,但不幸的是,我的笔记本电脑中没有。
    • 播种一次还是过一段时间再播种,取决于具体的RNG。 NIST 指定 PRNG 重新播种,但许多软件实现没有。围绕单例重构代码是一个可怕的想法,尤其是在多线程实现上;最好解决问题的根源:由于缺乏熵而导致播种缓慢。如果您使用单例,请使用它为其他完全确定的 SecureRandom 实现提供种子。不过,这种设计可能需要相当多的知识。
    • @MaartenBodewes 这些都是好点。如果实现是阻塞的,等待系统熵,我认为在应用程序中将其视为单例并不是一个可怕的想法,因为底层源实际上是单例。但是使用那个实例来播种其他实例是一个很好的建议,即使很复杂。我不确定,但我认为 SecureRandom 的 Sun(然后是 Oracle)提供商在过去 10 年的熵收集过程中发生了几次变化。
    • 我很确定它已经更改了很多次,以至于我不会尝试将所有更改都放在此评论中:)。缓慢的SecureRandom 仍然不太可能成为问题,但系统中的低熵将始终是问题。使用单例将创建强耦合代码,这是一种设计反模式。因此,使用时应格外小心;如果你想解决这个问题,你最好把代码中的所有引用都颠倒过来。
    【解决方案14】:

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

    如果您不需要这种对随机性的全面保证,那么可能需要适当的性能权衡。我倾向于同意Dan Dyer's response 来自 Uncommons-Maths 或 Fortuna 的 AESCounterRNG(其作者之一是密码学专家 Bruce Schneier)。我从来没有使用过,但乍一看,这些想法似乎很有名。

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

    编辑:这是我在网上找到的一些cryptographic course notes on RNG,看起来与这个主题非常相关。

    【讨论】:

    • “Fortuna(其作者之一是密码学专家 Bruce Schneier)”——另一位是密码学专家 Niels Ferguson :-)
    【解决方案15】:

    使用安全随机作为循环算法的初始化源;然后,您可以使用 Mersenne twister 进行批量工作,而不是 UncommonMath 中的那个,它已经存在了一段时间并且被证明比其他 prng 更好

    http://en.wikipedia.org/wiki/Mersenne_twister

    确保不时刷新用于初始化的安全随机数,例如,您可以为每个客户端生成一个安全随机数,每个客户端使用一个 mersenne twister 伪随机数生成器,以获得足够高的随机化程度

    【讨论】:

    • 这个答案是错误的:梅森捻线器不是一个安全的随机数生成器。对于Random,这将是一个很好的算法,但对于SecureRandom 则不是。
    【解决方案16】:

    如果您的硬件支持它,请尝试using Java RdRand Utility,我是其中的作者。

    它基于 Intel 的 RDRAND 指令,比 SecureRandom 快​​大约 10 倍,并且对于大容量实施没有带宽问题。


    请注意,此实现仅适用于提供指令的那些 CPU(即,当设置了 rdrand 处理器标志时)。您需要通过RdRandRandom() 构造函数显式实例化它;没有具体的Provider 实现。

    【讨论】:

    • 您可能想阅读 people.umass.edu/gbecker/BeckerChes13.pdf 并确保从不使用英特尔 RDRAND 数据。始终将其与其他一些不可预测的数据混合在一起,例如 aRC4 流密码的输出(从 /dev/urandom 播种,并且由于已知偏差而丢弃了前几 KiB 的输出)。
    • +1 奇迹。我认为RDRAND 是一个很好的来源,但它有点不可信。它绝对需要成为收藏家中的一种(无意冒犯大卫约翰斯顿)。
    • 我投了赞成票,修复了链接并提供了一些背景信息。如果您不同意,请回滚编辑。
    【解决方案17】:

    还有一点需要注意的是文件 lib/security/java.security 中的 securerandom.source 属性

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

    【讨论】:

      猜你喜欢
      • 2018-08-25
      • 1970-01-01
      • 1970-01-01
      • 2015-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-27
      • 2015-10-28
      相关资源
      最近更新 更多