【问题标题】:SHA1PRNG SecureRandom behavior is different after seeding on java11在 java11 上播种后,SHA1PRNG SecureRandom 行为不同
【发布时间】:2019-07-22 09:22:55
【问题描述】:

我正在使用java.security.SecureRandom"SHA1PRNG" 算法来生成加密密钥。这是用于加密次要数据的历史代码。然而,当我们从 java8 切换到 java11 时,我们的代码停止工作。这是重现这种情况的测试用例:

@Test
void srEncryptionSeedTest() throws NoSuchAlgorithmException
{
    final long versionSalt = 1850498708034063014L;
    final long customSalt  = -919666267416765972L;

    final SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    sr.setSeed(versionSalt);
    final long l1 = sr.nextLong();
    final long l2 = sr.nextLong();

    sr.setSeed(customSalt);
    final long k1 = sr.nextLong();
    final long k2 = sr.nextLong();

    // check l1 and l2
    Assert.assertEquals(l1, 6338935000439666355L);
    Assert.assertEquals(l2, -7355545655857008441L);

    // Seeding
    // check k1 and k2
    Assert.assertEquals(k1, -2226559466996804670L); // 
    Assert.assertEquals(k2, -3123855249705841778L);
}

这在 java11 上运行良好,但在 java8 上我们有 k1=-4273821888324981770k2=3053251164341917236,所以测试失败。如您所见,在产生相同数量的相同随机数后设置完全相同的种子后测试开始失败,所以我怀疑 RNG 的状态不同,但调试对我没有帮助(我无法理解 为什么不一样)。这可以在任何操作系统上轻松复制。

关于 Java8 JVM 的一些事实:

java.vendor -> Oracle Corporation // same goes on OpenJDK builds
java.version -> 1.8.0_202-ea // same goes on 1.8.0_181
java.vm.info -> mixed mode
java.specification.version -> 1.8
java.runtime.name -> Java(TM) SE Runtime Environment

关于 Java11 JVM 的一些事实:

java.vendor -> AdoptOpenJDK
java.version -> 11.0.3
java.vm.info -> mixed mode
java.specification.version -> 11
java.runtime.name -> OpenJDK Runtime Environment

我们将不胜感激。

【问题讨论】:

  • SecureRandom 从未声称在运行时生成相同的值。 "SHA1PRNG" 也没有,因为该算法甚至没有被写下来——它似乎只是代码,并且在 Java SE、其他供应商运行时以及 Java 和 Android 之间存在实现差异。
  • 谢谢@MaartenBodewes!我会在 Android 上仔细检查并改进我的问题。额外的问题:Java 中是否有任何可靠的 RNG 可以满足我的要求?
  • 我必须检查一下,但this one 似乎是最合适的 - 我会让你寻找它的名字。他们检查测试向量,这些向量应该在种子更新时有结果。因此,在这种情况下,唯一的问题可能是 NIST 调用和 Java API 之间的转换。
  • 如果您想生成加密密钥,请使用基于密码的密钥派生函数(如 PBKDF2),或者如果您需要加盐,请使用 HMAC-SHA256 以盐为密钥,但永远不要误用 PRNG!!!
  • 谢谢@Robert,我会考虑开发下一个项目,但总的来说这不是我的代码,不幸的是它必须解密一些已经加密的数据(最初的目的是JSON 配置更难修改给用户),所以我现在不能改变方法(我希望我可以,真的)。我想我找到了行为不同的确切原因,我最好仔细检查并回答我自己的问题。

标签: java security random cryptography sha


【解决方案1】:

[免责声明]: 不要这样做(除非您想要向后兼容)。如果您想要可预测性,您应该基于可靠的 RNG 实施您的解决方案,我也是。但不幸的是,我们必须支持旧文件版本的格式,这些文件也不包含任何敏感或个人数据,但我们不希望用户更改此数据,因为它以类似文本的格式存储,因此很容易更改。

我没有实现自己的“SHA1PRNG”,因为它太难了(在 cmets 中提到过)。相反,我破解了较新的 PRNG 版本,使其行为与旧版本完全相同。原因是因为 java9 OpenJDK 创建者决定制作新版本的 secureRandomSpi 以在每次调用 SecureRandom.setSeed() 时重置 remCount 整数字段的值,而旧版本没有这样做。

如果你想实现这个hack,你首先要做的是通过调用"SUN".equals(secureRandom.getProvider().getName()) && "SHA1PRNG".equals(secureRandom.getAlgorithm());来检查SecureRandom实例是否真的是SUN的“SHA1PRNG”,然后通过反射得到它的SPI并保存它的remCount 字段。然后您可以调用setSeed(),然后将保存的值安装回remCount 字段。我不想在这里发布这个晦涩的代码,但你明白了。谢谢。

【讨论】:

  • 请注意,算法可能仍然会发生变化,尽管现在这可能不是问题,因为有更好的 DRBG 可用。
猜你喜欢
  • 2015-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 2014-07-30
  • 2016-12-19
相关资源
最近更新 更多