硬核 - Java 随机数相关 API 的演进与思考(上2)

简介: 硬核 - Java 随机数相关 API 的演进与思考(上2)
+关注继续查看

SEED 的来源


由于 JDK 中所有的随机算法都是基于上一次输入的,如果我们使用固定 SEED 那么生成的随机序列也一定是一样的。这样在安全敏感的场景,不够合适,官方对于 cryptographically secure 的定义是,要求 SEED 必须是不可预知的,产生非确定性输出。


在 Linux 中,会采集用户输入,系统中断等系统运行数据,生成随机种子放入池中,程序可以读取这个池子获取一个随机数。但是这个池子是采集一定数据后才会生成,大小有限,并且它的随机分布肯定不够好,所以我们不能直接用它来做随机数,而是用它来做我们的随机数生成器的种子。这个池子在 Linux 中被抽象为两个文件,这两个文件他们分别是:/dev/random/dev/urandom。一个是必须采集一定熵的数据才放开从池子里面取否则阻塞,另一个则是不管是否采集够直接返回现有的。


在 Linux 4.8 之前:


微信图片_20220625194150.jpg


在 Linux 4.8 之后:


微信图片_20220625194154.jpg


在熵池不够用的时候,file:/dev/random会阻塞file:/dev/urandom不会。对于我们来说,/dev/urandom 一般就够用,所以一般通过-Djava.security.egd=file:/dev/./urandom设置 JVM 启动参数,使用 urandom 来减少阻塞。

我们也可以通过业务中的一些特性,来定时重新设置所有 Random 的 SEED 来进一步增加被破解的难度,例如,每小时用过去一小时的活跃用户数量 * 下单数量作为新的 SEED。


测试随机算法随机性


以上算法实现的都是伪随机,即当前随机数结果与上一次是强相关的关系。事实上目前基本所有快速的随机算法,都是这样的

并且就算我们让 SEED 足够隐秘,但是如果我们知道算法,还是可以通过当前的随机输出,推测出下一个随机输出。或者算法未知,但是能从几次随机结果反推出算法从而推出之后的结果。

针对这种伪随机算法,需要验证算法生成的随机数满足一些特性,例如:

  • period 尽可能长:a full cycle 或者 period 指的是随机序列将所有可能的随机结果都遍历过一遍,同时结果回到初始 seed 需要的结果个数。这个 period 要尽可能的长一些。
  • 平均分布(equidistribution),生成的随机数的每个可能结果,在一个 Period 内要尽可能保证每种结果的出现次数是相同的。否则,会影响在某些业务的使用,例如抽奖这种业务,我们需要保证概率要准。
  • 复杂度测试:生成的随机序列是否够复杂,不会有那种有规律的数字序列,例如等比数列,等差数列等等。
  • 安全性测试:很难通过比较少的结果反推出这个随机算法。

目前,已经有很多框架工具用来针对某个算法生成的随机序列进行测试,评价随机序列结果,验证算法的随机性,常用的包括:

Java 中内置的随机算法,基本都通过了 testU01 的大部分测试。目前,上面提到过的优化算法都或多或少的暴露出一些随机性问题。目前, Java 17 中的 LXM 算法是随机性测试中表现最好的注意是随机性表现,而不是性能


Java 中涉及到的所有随机算法(不包括 SecureRandom)



image.png


psla14.pdf

为什么我们在实际业务应用中很少考虑随机安全性问题


主要因为,我们一般做了负载均衡多实例部署,还有多线程。一般每个线程使用不同初始 SEED 的 Random 实例(例如 ThreadLocalRandom)。并且一个随机敏感业务,例如抽奖,单个用户一般都会限制次数,所以很难采集够足够的结果反推出算法以及下一个结果,而且你还需要和其他用户一起抽。然后,我们一般会限制随机数范围,而不是使用原始的随机数,这就更大大增加了反解的难度。最后,我们也可以定时使用业务的一些实时指标定时设置我们的 SEED,例如:,每小时用过去一小时的(活跃用户数量 * 下单数量)作为新的 SEED。

所以,一般现实业务中,我们很少会用 SecureRandom。如果我们想初始 SEED 让编写程序的人也不能猜出来(时间戳也能猜出来),可以指定随机类的初始 SEED 源,通过 JVM 参数 -Djava.util.secureRandomSeed=true。这个对于所有 Java 中的随机数生成器都有效(例如,Random,SplittableRandom,ThreadLocalRandom 等等)

对应源码:

static {
        String sec = VM.getSavedProperty("java.util.secureRandomSeed");
        if (Boolean.parseBoolean(sec)) {
            //初始 SEED 从 SecureRandom 中取
            // SecureRandom 的 SEED 源,在 Linux 中即我们前面提到的环境变量 java.security.egd 指定的 /dev/random 或者 /dev/urandom
            byte[] seedBytes = java.security.SecureRandom.getSeed(8);
            long s = (long)seedBytes[0] & 0xffL;
            for (int i = 1; i < 8; ++i)
                s = (s << 8) | ((long)seedBytes[i] & 0xffL);
            seeder.set(s);
        }
    }

所以,针对我们的业务,我们一般只关心算法的性能以及随机性中的平均性,而通过测试的算法,一般随机性都没啥大问题,所以我们只主要关心性能即可

针对安全性敏感的业务,像是 SSL 加密,生成加密随机散列这种,则需要考虑更高的安全随机性。这时候才考虑使用 SecureRandom。SecureRandom 的实现中,随机算法更加复杂且涉及了一些加密思想,我们这里就不关注这些 Secure 的 Random 的算法了


相关文章
|
6天前
|
JSON Java 测试技术
Elasticsearch常用Java API编程 2
Elasticsearch常用Java API编程
16 0
|
6天前
|
JSON Java 测试技术
Elasticsearch常用Java API编程 1
Elasticsearch常用Java API编程
24 0
|
10天前
|
Java API
Java 中如何使用 Stream API?
Java 中如何使用 Stream API?
|
11天前
|
Java API 数据安全/隐私保护
Elasticsearch Java API Client 开发
本场景主要介绍如何使用 Elasticsearch Java API Client 进行开发,实现常用的 CRUD 操作。
41 0
|
15天前
|
存储 Java API
Java之API详解之BigDecimal类的详细解析
Java之API详解之BigDecimal类的详细解析
22 0
|
15天前
|
存储 Java API
Java之API详解之Biginteger类的详解
Java之API详解之Biginteger类的详解
30 0
|
15天前
|
Java API
Java之API详解之Objects类的详细解析
Java之API详解之Objects类的详细解析
13 0
|
15天前
|
Java API 开发工具
Java之API详解之Object类的详细解析(下)
Java之API详解之Object类的详细解析(下)
11 0
|
15天前
|
Java API 开发工具
Java之API详解之Object类的详细解析(上)
Java之API详解之Object类的详细解析
12 0
|
15天前
|
Java API
Java之API详解之Runtime的详细解析
Java之API详解之Runtime的详细解析
19 0
Java之API详解之Runtime的详细解析
推荐文章
更多