SecureRandom与NativePRNG相比,SHA1PRNG的区别是什么?

47

我需要生成密码学上强壮的随机数和字节数组。为此,我正在使用Java的SecureRandom类。但是我不确定在密码学强度方面选择哪种PRNG算法。

以下哪个实例生成更不可预测的数字?还是它们相同?

SecureRandom nativePrng = SecureRandom.getInstance("NativePRNG")
SecureRandom sha1Prng = SecureRandom.getInstance("SHA1PRNG")

此外,我们可以使用“SUN”提供程序生成这些实例(例如,SecureRandom.getInstance("SHA1PRNG", "SUN"))。这样做有什么区别吗?

先行谢过。

3个回答

74
TL;DR: 当您不确定时,请使用 new SecureRandom() 并让系统自行决定。长期密钥生成可能使用SecureRandom.getInstanceStrong()
请不要期望随机数生成器在运行时应用程序中生成特定的输出序列,即使您自己进行种子处理也是如此。
使用随机数生成器时很难说哪个是最好的。Linux和大多数Unix系统都有一个非常好的随机数生成器,因此使用/dev/random/dev/urandom,即"NativePRNG",不会有什么问题。使用/dev/random的问题在于它会一直阻塞,直到足够的熵可用。因此,除非您对密钥生成有特殊要求,否则我建议不要使用它。
"SHA1PRNG"使用哈希函数和计数器,以及种子。算法相对简单,但描述不够清晰。通常认为它是安全的。由于仅在启动期间从系统生成器之一种子化,并因此需要较少调用内核,因此可能消耗更少资源-在我的双核Ubuntu笔记本电脑上运行约比"NativePRNG"(配置使用/dev/urandom)快9倍。两者似乎只会占用我的笔记本电脑的一个内核(有时会切换到另一个内核,这可能是内核调度造成的)。如果需要高性能,请选择这个版本,特别是在特定系统配置中/dev/urandom设备较慢的情况下。 请注意,在已停用的Apache Harmony实现中出现的"SHA1PRNG"与SUN提供程序中的版本(由Oracle在标准Java SE实现中使用)不同。位于Jakarta中的版本也用于旧版本的Android。虽然我还没有进行全面评审,但看起来并不太安全。" 编辑:我对此并不完全错误, SHA1PRNG 已被证明在版本<4.2.2中不是伪随机数生成器,更多信息请看这里

请注意,"SHA1PRNG" 不是 Java SE 的实现要求。在大多数运行时中它都将存在,但直接从代码中引用它会使您的代码不够可移植。


现在(从Java 9开始),OpenJDK和Oracle JDK也包含多个实现,这些实现简称为“DRBG”。它们实现了NIST在SP-108中指定的动态随机位生成器列表。但这些不是Java实现的要求,如果需要符合FIPS的随机数生成器,则可以使用它们。然而,它们并没有改变这里的建议;如果开发人员认为这些实现比默认实现更好,那么他们只需将其作为默认实现即可。SecureRandom的契约不会改变:它仍然需要生成随机数。过去已经对默认算法做出了更改。
一般来说,要求特定提供者不是一个好主意。指定提供者可能会影响互操作性;例如,并不是每个Java运行时都可以访问SUN提供者——Android肯定没有。它还会使你的应用程序在运行时更加缺乏灵活性,即你不能将提供者放在列表中更靠前并使用它。
因此,只有在依赖于其提供的某些功能时才指示提供者。例如,如果您有一个生成随机数的特定硬件设备或已获得FIPS认证的密码库,则可能希望指定提供者。如果必须指定提供程序,则将算法/提供程序作为应用程序的配置选项可能是一个好主意。
不指定提供者的想法也在Android developer Security Blog中提出。
请尽量避免选择任何特定的随机生成器,而是选择空参数构造函数:new SecureRandom(),让系统选择最佳的随机数生成器。如果您需要长期密钥生成等特定要求,则可以在Java 8及更高版本中使用新的可配置 SecureRandom.getInstanceStrong()
不要缓存 SecureRandom 实例,只需让它们最初自行进行种子生成并让 VM 处理即可。我没有看到操作上有明显的差异。

何时不应使用SecureRandom

总的来说,我强烈建议只将随机数生成器用于随机数生成,不要用于其他任何目的。即使您可以自己提供种子,并且即使您选择了Sun的SHA1PRNG,也不要指望能够从随机数生成器中提取相同的随机数序列。因此,请不要将其用于从密码派生密钥等用途。

如果确实需要重复序列,则使用流密码并使用种子信息作为密钥和IV。加密由零组成的明文以检索伪随机值的密钥流。或者,您可以使用可扩展输出函数(XOF),例如SHAKE128或SHAKE256(如果可用)。

如果可用的随机数生成器提供的性能不足且安全性不是问题,您可能希望考虑使用不同的非安全随机数生成器,而不是使用SecureRandom。像Mersenne Twister算法或Random类实现的算法这样的非安全随机数生成器已经被优化为简单和快速,而不是安全。没有一个SecureRandom实现会像非安全随机数生成器一样快。
可以扩展SecureRandom类并将确定性种子随机实现插入到库调用中。这样库就可以检索具有定义良好输出的伪随机数生成器。但应注意,随机数生成器可能会以不同的方式被算法使用。例如,RSA可能会切换到更好的优化方式来查找质数,而DES密钥可能会生成具有调整或直接计算的奇偶校验位。

3
如果您盲目信任系统选择的任何SecureRandom服务,是否会打开安全漏洞? - neurite
2
不,其实不是这样的。如果系统不可信,则随机数生成器只是问题的一小部分。此外,其他随机数生成器通常由操作系统的随机数生成器种子生成。因此,它们仍然依赖于系统RNG的安全性。唯一的解决方法是拥有自己的熵源。新的英特尔芯片具有RDRAND指令,但在Java的情况下,您需要先进行本地化才能使用它。 - Maarten Bodewes
1
@cygnusv 的确如此。但如果您不定义 PRNG,就会得到这样的结果。SUN的实现随着时间的推移而改变,Apache Harmony(旧版Android)的实现是不兼容且完全不安全的。 - Maarten Bodewes
2
@MaartenBodewes的回答非常好,但我更喜欢自行查看源代码,这里是JDK8源代码: sun.security.provider.SecureRandom, sun.security.provider.NativePRNG, sun.security.provider.SeedGenerator - bric3
3
1)算法未知,可能(并且确实)在不同的实现之间发生变化,种子可能不会作为状态的唯一输入使用,操作系统可能会预先设置算法。 2)使用PBKDF2,该功能已内置于Java / JCE中。 - Maarten Bodewes
显示剩余4条评论

5

来自这里的参考信息:

适用于Solaris/Linux的本地 PRNG 实现。它与/dev/random和/dev/urandom进行交互,因此仅在这些文件存在时才可用。否则,将使用SHA1PRNG而不是此类。

SUN提供程序可能会作为默认值使用(主要取决于提供程序的顺序)。


0

我的两分钱。

  1. 优先使用 NativePRNG 而非 SHA1PRNG

  2. 配置 JVM 参数以从 /dev/urandom 获取熵,例如:

     -Djava.security.egd=file:/dev/./urandom 
    
  3. 不要频繁重新播种

原因:周期性的熵注入发生在NativePRNG中,与SHA1PRNG相比增加了更多的随机性。

通过提供来自/dev/urandom的熵源而不是/dev/random,您可以避免在熵生成期间阻塞对RNG的调用。

编辑:在Java 8之后,不需要使用-Djava.security.egd参数。


在Windows上是否存在NativePRNGNativePRNGNonBlocking?如果是,它们使用什么算法?如果没有,Windows-PRNG使用什么算法? - Melab
SHA1PRNG 可以被 Windows 使用。 - Ravindra babu
那并没有回答我的任何问题。 - Melab
它解释了为什么NativePRNG优于SHA1。一旦我们偏爱NativePRNG,第二个问题就不相关了。 - Ravindra babu
但我并没有询问任何事情的原因,也没有询问关于SHA1PRNG的任何问题。我想知道NativePRNG/NativePRNGNonBlocking在Windows上是否是有效的名称,如果它们是有效的,它们使用什么算法,以及Windows-PRNG使用什么算法。比如,Windows-PRNG是否使用Fortuna - Melab
显示剩余2条评论

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