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

2023-01-11 17:12:03 +08:00
 jaredyam
Random random = new Random();
random.setSeed(new SecureRandom().nextInt());
random.nextInt();

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

3421 次点击
所在节点    Java
28 条回复
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
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
2023-01-12 10:13:48 +08:00
这实际上就是 /dev/urandom 的随机数生成方式,用 securerandom 做种子,然后跑纯靠计算的随机数算法
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
2023-01-12 20:24:50 +08:00
/dev/random 无疑是密码学的更好选择。即使 /dev/urandom 相对安全,也没有理由选择后者。

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

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

事实:不。即使不考虑可用性和随后的用户操纵等问题,熵“耗尽”的问题也是一个稻草人。大约 256 位的熵足以在很长很长一段时间内获得计算上安全的数字。
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
2023-01-13 12:35:58 +08:00
另外真随机是完全无规律的随机,所以只适合用作密码学相关以及设置伪随机的种子,在业务场景下,希望获得 均匀分布、正态分布 等特性的随机数需要对应的伪随机配合,所以 业务场景 真随机种子 + 伪随机算法 就是最优解没有之一
kkhaike
2023-01-13 12:39:32 +08:00
@CEBBCAT 上面忘了 at 。。。不好意思

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/908198

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX