你能 "clone" 一个 java SecureRandom 对象吗? - 如何获得可克隆的强大RNG?

Can you "clone" a java SecureRandom object? - How to get a cloneable strong RNG?

我有一个需要长随机位序列的应用程序。

因为我依赖这些序列 "truly random",所以我想使用 SecureRandom。

遗憾的是,我需要每个这样的序列两次。

我无法将它们保存在 RAM 中,因为它们太长了。

我不想将它们写入硬盘,因为写入和读取硬盘会花费时间和硬盘 space 并且会使代码变得不必要的复杂。

但是,由于某些生成器生成的 "random" 位序列确定性地取决于生成器创建时所处的状态,因此(也许)可以保存并重新创建新生成的状态SecureRandom 对象存在,允许 重新创建位序列。

主要问题是:

(如何)你能保存 SecureRandom 随机数生成器的整个状态吗?克隆就够了吗? (可能有一些 OS 状态也起作用,例如,当前时间)是否有任何其他强大的 RNG 可以克隆,以便原始和克隆产生相同的输出?

另外,我对正常的随机数生成器创建什么样的模式很感兴趣,这使得它们不安全,以及是否可以如上所述保存和恢复它们的状态。

编辑:

我为此创建了一个 LCG。

但是,我认为它不能正常工作。

它是否创建了一个 "strong" 伪随机位序列?

当它连续多次产生相同的值时,它变得可疑了。 例如:

错误 错误的 真的 真的 真的 真的 错误的 真的 错误的 错误的 错误的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真的 真

public class DeterministicRandom{
    private BigInteger state;
    private BigInteger a;
    private BigInteger c;
    private static final int o=32;
    private static final BigInteger m=BigInteger.ONE.shiftLeft(o*8);
    public DeterministicRandom(){
        a=RandomUtils.randomBigInt(o);
        c=RandomUtils.randomBigInt(o);
        state=RandomUtils.randomBigInt(o);
    }
    public DeterministicRandom(DeterministicRandom r){
        a=r.a;
        c=r.c;
        state=r.state;
    }
    public boolean nextBoolean(){
        boolean r=state.testBit(o*8-7);
        state=state.multiply(a).add(c).mod(m);
        return r;
    }
}
public class RandomUtils {
    private static Random random = new Random();
    public static byte[] nextBytes(byte[] d){
        random.nextBytes(d);
        return d;
    }
    public boolean randomBit(){
        return random.nextBoolean();
    }
    public static BigInteger randomBigInt(int bytes){
        byte[] d=new byte[bytes+1];
        random.nextBytes(d);
        d[0]=0;
        BigInteger x=new BigInteger(d);
        return x;
    }
}

我认为您不能通常 SecureRandom 可克隆的实例,或提供与原始序列相同序列的克隆。

问题在于,典型的 Java 平台提供安全随机数的多种实现,当您调用 new SecureRandom() 时,您会获得默认实现。这可能是 PRNG,也可能是使用 "real" 随机源的 RNG。在后一种情况下,即使 class 支持 clone(),克隆对象也不会给出与原始对象相同的随机序列。

我能想到几个解决方案(一般来说)。


第一个解决方案是创建自己的 Random class 来包装 SecureRandom 实例。特殊情况是您的包装器 class 需要 记录 它发出的随机数流。然后你需要一个 reset() 方法来让你的 PRNG 切换到使用记录的数字。

缺点是您需要足够的内存来保存所有数字。 (根据表示,这可能需要大量内存。例如,如果您在 ArrayList<Integer> 中记录 N 个整数,最坏的情况是 Integer 对象的 N x 24 字节和 3 的峰值x N x 8 字节用于 ArrayListint[] 更密集,但更难管理。)

我看到您说过序列太长而无法在您的用例中保存在内存中。您可以考虑将它们写入文件,但即使那样也不能无限扩展。


第二种解决方案是利用 SecureRandom classes 能力 select 算法并重新播种。

SecureRandom seeder = new SecureRandom();
byte[] seed = seeder.generateSeed(NOS_BYTES);

SecureRandom prng = SecureRandom.getInstance("SHA1PRNG");
prng.setSeed(seed);

SecureRandom prng2 = SecureRandom.getInstance("SHA1PRNG");
prng2.setSeed(seed);

// prng1 and prng2 created as above should generate the same sequences

NOS_BYTES设置为您要使用的种子字节数。或者您可以生成种子 "by hand",或从文件中读取它。

请注意,您无法像在使用 new SecureRandom() 获得的实例上那样可靠地设置种子,并且您无法可靠地为现有实例重新播种 1

您可能会使用其他 PRNG 算法,具体取决于您的 Java 平台配置的安全提供程序。

更多信息请参考SecureRandomjavadoc

我测试了上述方法,并在我的机器上使用 Java 11,prngprng2 对象生成了相同的 int 值序列......直到我打了 ^C.

1 - 问题的症结在于 setSeed 的 javadoc 是这样说的:"The given seed supplements, rather than replaces, the existing seed."。在其他地方,它表示构造函数 return 一个已播种的对象。幸运的是,它还说 getInstance 方法 return 尚未播种的对象。


最后,我从您更新的问题中了解到您正在考虑使用 "roll-your-own" PRNG,使用 BigInteger 实现。

小心!

很容易实现一个没有你想象的那么好的PRNG。我不会那样做。但是,如果您决定沿着这条路走下去,您应该阅读 PRNG 的理论及其属性以及 运行 一批(相关的)关于您的实现的统计测试。

(如果你打算发表你的结果,你的论文应该提到你实现了自己的PRNG,并且你应该把PRNG源代码和统计测试结果提供给读者查看。为了开放和科学的可重复性。在我看来。)

您似乎需要用于机器学习或 Monte Carlo 抽样的随机数。看来您也考虑过 java.util.Random 并发现它不适合您的目的。在那种情况下,SecureRandom 远不是最好的选择。特别是,没有标准方法来实现 SecureRandom 的“SHA1PRNG”提供程序,并且在应用程序关心可重现“随机性”的情况下不应使用 SecureRandom。另见 Encryption algorithm giving different results on Android 2.1 and versions above 2.1

相反,您需要 高质量的 PRNG,并且有很多 high-quality RNG algorithms 可用。

在 Java 中,两个 examples 包括 it.unimi.dsi/dsiutilsorg.apache.commons/commons-rng-simple 工件,其中包括可播种 PRNG 的实现,例如 xoroshiro128++xoshiro256**.