31

我们的团队正在使用 SecureRandom 生成密钥对列表(SecureRandom 被传递给 KeyPairGenerator)。我们无法就使用以下两个选项中的哪一个达成一致:

  1. 每次我们需要生成密钥对时创建一个新实例

  2. 初始化一个静态实例并将其用于所有密钥对

哪种方法通常更好,为什么

补充:我的直觉是第二种选择更安全。但我唯一的论点是基于伪随机性源自当前时间戳的假设的理论攻击:有人可能会看到密钥对的创建时间,猜测周围时间间隔内的时间戳,计算可能的伪随机序列,并获得关键材料。

补充:我关于基于时间戳的确定性的假设是错误的。这就是 Random 和 SecureRandom 之间的区别。所以,看起来答案是:就安全性而言,这并不重要。

4

8 回答 8

18

java.util.Random类不同,java.security.SecureRandom类必须在每次调用时产生非确定性输出。

这意味着,在这种情况下java.util.Random,如果您每次需要一个新的随机数时都使用相同的种子重新创建一个实例,那么每次基本上都会得到相同的结果。但是,SecureRandom保证不会这样做 - 因此,每次创建单个实例或创建一个新实例都不会影响它生成的随机字节的随机性。

那么,从正常的良好编码实践的角度来看,为什么要创建太多实例呢?

于 2008-11-17T14:15:07.130 回答
17

对于SecureRandom,您可能需要考虑通过如下调用偶尔重新播种(在大多数情况下使用系统熵):

mySecureRandom.setSeed(mySecureRandom.generateSeed(someInt));

以便给潜在的攻击者一些少于无限的时间来发现您的密钥。

正义联盟博客上有一些关于这一考虑的精彩文章。

于 2010-07-19T04:08:34.877 回答
6

初始化一个静态实例并将其用于所有密钥对。它不会或多或少是随机的。

于 2008-11-17T14:07:33.027 回答
5

SecureRandom一代都是从某个熵池中播种的。根据所使用的操作系统,这可能是操作系统维护的熵池,例如/dev/randomLinux,或者可能是 JVM 制作的东西。在一些较早的实现中,Sun JVM 用于生成多个线程并使用它们的时间数据来创建种子。

在每次调用时创建一个新SecureRandom的可能会导致应用程序变慢,因为创建种子可能会阻塞。最好重用静态创建的实例,但确保在从中提取固定数量的随机字节后重新设置它。

您可能希望在实例上创建一个包装器,该包装器计算在或调用SecureRandom中提取的字节数,并在多个字节后使用系统熵池重新植入内部实例。nextBytesgenerateSeedSecureRandom

然而,包装器方法在 Linux 上的 Java 上是不可能的,因为SecureRandom您从 new 获得的实例SecureRandom()只不过是一个包装器/dev/random,每次调用nextBytesgenerateSeed实际上耗尽操作系统熵池。在 Linux 和 Solaris 上,最好使用 JCE 提供程序进行SecureRandom创建。

于 2009-08-20T09:10:13.553 回答
3

除了加密安全的 PRNG,我不会依赖 SecureRandom。Gowri 从 javadocs 中使用的完整引用是:

此外,SecureRandom 必须产生非确定性输出,因此要求种子材料是不可预测的,并且 SecureRandom 的输出是加密强序列,如 RFC 1750:安全随机性建议中所述。

从中并不清楚真正的期望是什么 - RFC 1750 详细说明了使用硬件来增强随机数生成,但 javadocs 说“因此要求种子材料是不可预测的”,这似乎与此相矛盾。

最安全的假设是您的 SecureRandom 实现只是一个密码安全的 PRNG,因此您的密钥并不比您使用的随机种子更安全。因此,为每个密钥使用一个新的(唯一的、真正随机的)种子来初始化一个新的 SecureRandom 将是最安全的选择。

于 2008-11-17T17:14:20.857 回答
1

一次应该就够了。我的经验也是,初始化 SecureRandom 类型生成器有时也会很慢(由于如何实现随机性),因此您应该考虑到这一点。

于 2008-11-17T19:08:50.940 回答
0

我决定问Java编译器。简短的回答是,是的,重用SecureRandom对象有一些性能优势,但不是更好或更糟的是实际随机性。这纯粹是一个调优问题。不是安全问题。

但是请注意,JIT 需要一段时间才能启动,这样您才能看到好处。要点是,对于重度/频繁使用,绝对要重复使用该对象。对于不经常使用的情况,您最好每次都使用一个新对象。

结果

warm up 
-----------------------------
default seed - re-use - 1807 ms
explicit seed - re-use - 835 ms
constant seed - new every time - 1044 ms
default seed - new every time - 1621 ms
-----------------------------
interation 0
-----------------------------
default seed - re-use - 412 ms
explicit seed - re-use - 418 ms
constant seed - new every time - 955 ms
default seed - new every time - 1676 ms
-----------------------------
interation 1
-----------------------------
default seed - re-use - 389 ms
explicit seed - re-use - 369 ms
constant seed - new every time - 893 ms
default seed - new every time - 1498 ms

来源

package foo;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Set;

import org.junit.BeforeClass;
import org.junit.Test;

public class SecureRandomTest {
    
    static long elapsedMillis( long startNs ) {
        long now = System.nanoTime();
        return (now - startNs) / 1_000_000;
    }
    final static long seed = 123456789123456L;
    final static int nIter = 1000000;
    
    public static void main(String[] args) {
        warmup();
        SecureRandomTest test = new SecureRandomTest();
        for ( int ix = 0; ix < 5; ++ix ) {
            test.run(ix);
        }
    }
    
    void run(int ix) {
        System.out.printf( "interation %d\n-----------------------------\n", ix);
        secure_random_default_seed_reuse();
        secure_random_constant_seed_reuse();
        secure_random_constant_seed();
        secure_random_default_seed();
        System.out.println("-----------------------------");
    }

    /* Warm up JVM/JIT */
    @BeforeClass
    public static void warmup() {
        new SecureRandomTest().run(-1);
    }

    @Test
    public void secure_random_constant_seed() {
        long started = System.nanoTime();
        int nDupes = 0, ix = 0;
        Set<Long> generated = new HashSet<>(nIter);
        for ( /**/; ix < nIter; ++ix) {
            SecureRandom rand = new SecureRandom();
            rand.setSeed(seed);
            long xRand = rand.nextLong();
            if ( !generated.add(xRand) ) {
                ++nDupes;
            }
        }
        assertEquals( "Unexpected # of dupes " + nDupes + ", ix == " + ix, nIter-1, nDupes );
        System.out.printf( "constant seed - new every time - %d ms\n", elapsedMillis(started) );
    }

    @Test
    public void secure_random_constant_seed_reuse() {
        long started = System.nanoTime();
        int nDupes = 0, ix = 0;
        SecureRandom rand = new SecureRandom();
        rand.setSeed(seed);
        Set<Long> generated = new HashSet<>(nIter);
        for ( /**/; ix < nIter; ++ix) {
            long xRand = rand.nextLong();
            if ( !generated.add(xRand) ) {
                ++nDupes;
            }
        }
        assertTrue( "Unexpected # of dupes " + nDupes + ", ix == " + ix, 0 == nDupes );
        System.out.printf( "explicit seed - re-use - %d ms\n", elapsedMillis(started) );
    }

    @Test
    public void secure_random_default_seed() {
        long started = System.nanoTime();
        int nDupes = 0, ix = 0;
        Set<Long> generated = new HashSet<>(nIter);
        for ( /**/; ix < nIter; ++ix) {
            long xRand = new SecureRandom().nextLong();
            if ( !generated.add(xRand) ) {
                ++nDupes;
            }
        }
        assertTrue( "Unexpected # of dupes " + nDupes + ", ix == " + ix, 0 == nDupes );
        System.out.printf( "default seed - new every time - %d ms\n", elapsedMillis(started) );
    }

    @Test
    public void secure_random_default_seed_reuse() {
        long started = System.nanoTime();
        int nDupes = 0, ix = 0;
        SecureRandom rand = new SecureRandom();
        Set<Long> generated = new HashSet<>(nIter);
        for ( /**/; ix < nIter; ++ix) {
            long xRand = rand.nextLong();
            if ( !generated.add(xRand) ) {
                ++nDupes;
            }
        }
        assertTrue( "Unexpected # of dupes " + nDupes + ", ix == " + ix, 0 == nDupes );
        System.out.printf( "default seed - re-use - %d ms\n", elapsedMillis(started) );
    }
}
于 2021-10-15T17:06:46.333 回答
-1

为什么要每次都创建一个新实例?不是那样会随机。我认为最好初始化一次并将其用于所有对。

于 2008-11-17T14:05:19.757 回答