在 java11 上播种后 SHA1PRNG SecureRandom 行为不同

SHA1PRNG SecureRandom behavior is different after seeding on java11

我正在使用 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

任何帮助将不胜感激。

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

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

如果你想实现这个 hack,你要做的第一件事就是通过调用 "SUN".equals(secureRandom.getProvider().getName()) && "SHA1PRNG".equals(secureRandom.getAlgorithm()); 检查 SecureRandom 实例是否真的是 SUN 的“SHA1PRNG”,然后通过以下方式获取它的 SPI反射并保存其 remCount 字段的值。然后您可以调用 setSeed() 并将保存的值安装回 remCount 字段。我不想在这里 post 这个晦涩难懂的代码,但你已经明白了。谢谢。