V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jaredyam
V2EX  ›  Java

这段随机数生成代码为什么这么写?

  •  
  •   jaredyam · 2023-01-11 17:12:03 +08:00 · 3646 次点击
    这是一个创建于 684 天前的主题,其中的信息可能已经有所发展或是发生改变。
    Random random = new Random();
    random.setSeed(new SecureRandom().nextInt());
    random.nextInt();
    

    是乱写的,还是有什么考虑?请大神指点一二。

    28 条回复    2023-01-13 12:39:32 +08:00
    chendy
        1
    chendy  
       2023-01-11 17:15:37 +08:00
    随机基础上再随机…
    其实直接 new SecureRandom().nextInt() 就行了
    因为种子一样随机的序列也是一样的,所以外面这一层 Random
    chendy
        2
    chendy  
       2023-01-11 17:15:54 +08:00
    @chendy #1 所以外面这一层 Random 没用了…
    Ediacaran
        3
    Ediacaran  
       2023-01-11 17:16:04 +08:00 via iPhone
    种子足够随机
    jaredyam
        4
    jaredyam  
    OP
       2023-01-11 17:18:55 +08:00
    @Ediacaran 原来这块代码种子的随机数位数也要更大些
    jaredyam
        5
    jaredyam  
    OP
       2023-01-11 17:19:43 +08:00
    @chendy 我现在是没想通它这个加层种子是想干啥,而且一个是 Random ,一个又是 Secure 的
    sunny352787
        6
    sunny352787  
       2023-01-11 17:20:51 +08:00
    总得给个种子吧,至于种子来源其实无所谓

    常用的当前时间做种子理论上来说确实有被攻击的可能,所以这样写算是更安全的写法
    不过实际上我觉得这人是想的有点多,除非是纯客户端程序在本地包个虚拟机强行改时间,不然的话用服务器当前时间戳作为随机数种子我觉得没啥问题
    sunny352787
        7
    sunny352787  
       2023-01-11 17:24:30 +08:00
    @jaredyam 通常的想法是 Random 要比 SecureRandom 性能高,SecureRandom 比 Random 更安全,这样用应该类似 https 的设计,用高安全性的 RSA 来传递密钥,然后用高性能的对称算法来执行加密过程,基本都是这种设计思路
    CEBBCAT
        8
    CEBBCAT  
       2023-01-11 17:27:17 +08:00   ❤️ 1
    我推测是希望获得安全的随机数,但又希望减少来自 SecureRandom 的阻塞。

    但如果说要评价写得好不好,应该在完成设计目标的前提下,考察随机性和必要性。即 SecureRandom().nextInt() 是否带来了足够的墒、new Random()的函数是不是安全的。以及可不可以直接使用 SecureRandom().nextInt()。
    jaredyam
        9
    jaredyam  
    OP
       2023-01-11 17:27:57 +08:00
    @sunny352787 所以你是说这个过程没问题是吧。

    > 总得给个种子吧

    这句话可以再解释下吗?和直接 Random.nextInt()有什么区别呢?也是出于安全的考虑?
    ysc3839
        10
    ysc3839  
       2023-01-11 17:28:17 +08:00 via Android
    SecureRandom 应该是用系统的安全随机数,Random 应该是纯算法的伪随机数。
    个人感觉这么做,如果后面是从 random 对象中取多个随机数的话,会降低安全性。如果只取一次问题不大。
    sunny352787
        11
    sunny352787  
       2023-01-11 17:28:24 +08:00
    @jaredyam 另外,SecureRandom 本身在 Linux 等系统下使用的是操作系统的熵计算,也就是真随机数,但熵源不足的情况下会造成阻塞
    ipwx
        12
    ipwx  
       2023-01-11 17:29:34 +08:00
    SecureRandom 的默认参数所使用的种子可能来自于( Posix 上)/dev/random 。而 /dev/random 是个有限的真随机序列,其内容来自于对机器硬件数据的采集。比如网卡的噪音、温度传感器、鼠标键盘动作等。

    通过 SecureRandom 得到一个普通伪随机生成器 Random 的初始 seed ,可以有效地得到一个不可预测的随机序列。
    sunny352787
        13
    sunny352787  
       2023-01-11 17:31:57 +08:00   ❤️ 1
    @jaredyam 过程没问题,而且确实比用时间做种子更安全

    大部分的语言的默认随机数生成器的实现都是需要设置一个种子的,不然默认种子为 0 ,也就是每次启动程序随机出来的序列都是一模一样的
    大部分随机数生成算法就是一个迭代公式,用上一个结果计算下一个随机数,那么第一个数从哪来呢?就是你给的种子呗
    tinybaby365
        14
    tinybaby365  
       2023-01-11 17:35:10 +08:00
    random.nextInt();只一次,不如 new SecureRandom().nextInt()

    random.nextInt();有限次,挺好

    random.nextInt();很多次,看实际情况判断
    CEBBCAT
        15
    CEBBCAT  
       2023-01-11 17:37:23 +08:00
    虽然还没人问,但我推荐阅读一下 /dev/urandom 和 /dev/random 的文章,好像和前几年那些 Linux 书本不同,现在即使在加密场景下,也推荐使用 /dev/urandom ,说是已经足够安全了,又不会阻塞。
    zankard
        17
    zankard  
       2023-01-11 18:11:56 +08:00   ❤️ 1
    https://metebalci.com/blog/everything-about-javas-securerandom/ 可以看看这个,使用 random 看你对安全性的要求,里面有一个破解程序,只需要知道连续两个随机数,很快就能算出 seed 了,这时候使用 securerandom 作为 seed 与否并不不能保证安全的。
    zankard
        18
    zankard  
       2023-01-11 18:19:46 +08:00
    @zankard 不是连续的随机数,应该也可以运算,就是计算量就更大了。
    realpg
        19
    realpg  
       2023-01-11 19:36:27 +08:00
    不搞密码学应用 不用考虑这些问题

    在座诸位 99.999999%用随机数的场景都只是需要一个随机一点的值来实现逻辑,而不是基于随机性进行安全化
    kkhaike
        20
    kkhaike  
       2023-01-11 21:16:00 +08:00
    这是对的。SecureRandom 应该是使用系统熵的真随机。使用这个作为伪随机的种子能够做到更好的效果。
    因为在分布式 /多实例场景下,使用时间作为种子也很容易得到初冲突。

    c++11 后也是这样的,参考 https://learn.microsoft.com/zh-tw/cpp/standard-library/random?view=msvc-170 最后一个实例
    go 也有使用 crypto/rand 设置 math/rand 种子的用法

    主要原因是真随机的获取非常慢。不适合在业务场景下使用。这种方式能够兼得随机性与速度的最好效果(甚至可以在多线程场景下分线程创建以避免锁开支)
    CEBBCAT
        21
    CEBBCAT  
       2023-01-11 23:32:25 +08:00
    @kkhaike #20 单论 Golang 来说,crypto/rand 的速度没有非常慢,大概是 math/rand 的一半。同样是读取 4KB 随机字节,crypto 花费 7.5us ,math 花费 3.3us 。

    goos: darwin
    goarch: arm64
    BenchmarkRandRead4KB-8 356580 3335 ns/op
    BenchmarkCryptoRead4KB-8 158610 7540 ns/op

    https://gist.github.com/Zhang-Siyang/cb10162e8f98e87041201d15aea89088
    Avn
        22
    Avn  
       2023-01-12 09:33:58 +08:00
    seed 相同的 Random 产生的序列也是相同的

    ```java
    public static void main(String[] args) {
    Random random1 = new Random();
    random1.setSeed(1L);
    Random random2 = new Random();
    random2.setSeed(1L);
    for (int i = 0; i < 3; i++) {
    System.out.println("random 1 - " + random1.nextInt());
    System.out.println("random 2 - " + random2.nextInt());
    }
    }
    ```

    ```shell
    random 1 - -1155869325
    random 2 - -1155869325

    random 1 - 431529176
    random 2 - 431529176

    random 1 - 1761283695
    random 2 - 1761283695
    ```

    把 seed 改成随机数可以避免这个现象
    wangyu17455
        23
    wangyu17455  
       2023-01-12 10:13:48 +08:00
    这实际上就是 /dev/urandom 的随机数生成方式,用 securerandom 做种子,然后跑纯靠计算的随机数算法
    h0099
        24
    h0099  
       2023-01-12 20:23:16 +08:00
    #23 @wangyu17455 https://www.2uo.de/myths-about-urandom/ 早已指出(机翻):

    /dev/urandom 是一个伪随机数生成器,一个 PRNG ,而 /dev/random 是一个“真”随机数生成器。

    事实: /dev/urandom 和 /dev/random 都使用完全相同的 CSPRNG (一种加密安全的伪随机数生成器)。它们仅在极少数方面有所不同,与“真正的”随机性无关。

    /dev/random 无疑是密码学的更好选择。即使 /dev/urandom 相对安全,也没有理由选择后者。

    ---
    因此,要明确一件事:/dev/random 和 /dev/urandom 都由同一个 CSPRNG 提供。根据一些估计,只有当它们各自的池耗尽熵时的行为不同:/dev/random 阻塞,而 /dev/urandom 没有。

    从 Linux 4.8 开始
    在 Linux 4.8 中,/dev/urandom 和 /dev/random 之间的等效性被放弃了。现在 /dev/urandom 输出不是来自熵池,而是直接来自 CSPRNG 。
    h0099
        25
    h0099  
       2023-01-12 20:24:50 +08:00
    /dev/random 无疑是密码学的更好选择。即使 /dev/urandom 相对安全,也没有理由选择后者。

    事实: /dev/random 有一个非常讨厌的问题:它会阻塞。

    但这很好!/dev/random 给出的随机性与其池中的熵一样多。/dev/urandom 会给你不安全的随机数,即使它早已耗尽熵。

    事实:不。即使不考虑可用性和随后的用户操纵等问题,熵“耗尽”的问题也是一个稻草人。大约 256 位的熵足以在很长很长一段时间内获得计算上安全的数字。
    kkhaike
        26
    kkhaike  
       2023-01-13 12:27:27 +08:00
    我说的是 真随机 的获取非常慢,因为涉及到操作系统要主动将熵值填入熵池,这个是很缓慢的。

    golang 的 crypt/rand 在某些系统下并不是纯正的真随机(纯真随机应该类似于 /dev/random 一样会在熵池被掏空时阻塞),golang 在不同的操作系统下的实现
    // On Linux, FreeBSD, Dragonfly and Solaris, Reader uses getrandom(2) if
    // available, /dev/urandom otherwise.
    // On OpenBSD and macOS, Reader uses getentropy(2).
    // On other Unix-like systems, Reader reads from /dev/urandom.
    // On Windows systems, Reader uses the RtlGenRandom API.

    1. getrandom(flags==0), 相当于 /dev/random ,https://github.com/torvalds/linux/blob/d9fc1511728c15df49ff18e49a494d00f78b7cd4/drivers/char/random.c#L1343-L1350 ,默认会阻塞等待熵值
    2. getentropy ,https://support.apple.com/zh-cn/guide/security/seca0c73a75b/web , 使用 Fortuna 算法,安全度很高的伪随机并使用熵源初始化,无阻塞
    3. RtlGenRandom ,https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom , 文档上就说是伪随机
    4. /dev/urandom ,不会阻塞,会在熵值耗尽使用 crng_fast_key_erasure 获得伪随机结果( chacha 加密结果的一部分) https://github.com/torvalds/linux/blob/d9fc1511728c15df49ff18e49a494d00f78b7cd4/drivers/char/random.c#L336-L341
    kkhaike
        27
    kkhaike  
       2023-01-13 12:35:58 +08:00
    另外真随机是完全无规律的随机,所以只适合用作密码学相关以及设置伪随机的种子,在业务场景下,希望获得 均匀分布、正态分布 等特性的随机数需要对应的伪随机配合,所以 业务场景 真随机种子 + 伪随机算法 就是最优解没有之一
    kkhaike
        28
    kkhaike  
       2023-01-13 12:39:32 +08:00
    @CEBBCAT 上面忘了 at 。。。不好意思
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1463 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 17:20 · PVG 01:20 · LAX 09:20 · JFK 12:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.