关于随机数的一点理解
在日常的工作中,经常会遇到关于随机数的问题。在此写一点随笔。欢迎指正。
随机数会在系统中会频繁使用,例如验证码、订单ID、密钥···。在Java中常使用的随机数生成方法有Random、SecureRandom
但是通常使用在安全场景下的随机数,如生成CSRF-Token、salt等,对随机数的随机性的要求很高,不然结果很容易预测,因此可能导致被攻击者击中。
1.1 random
Random一个简单的举例如下图,使用random的共有方法nextInt,生成了一个0~5之间的随机数。

Random是一个构造函数,参数可以传一个long类型的值,当传参为空构造的时候,实际上是使用System.nanoTime()也就是当前时间毫秒数的值为种子。
未指定种子时,每次运行下面代码生成不同的随机数:

指定种子为666,每次运行代码,得到的随机数都相同:

由此可见,种子的设置极为重要,将直接影响最终的随机数生成。
虽然可以使用系统时间作为种子,然后通过算法得出最终的随机数,但这其实是一种伪随机数。若种子相同,得出来的随机数也是相同的。在安全场景的使用下是比较危险的一件事。若指定种子,也要保证种子生成的随机性。
一个典型的例子,如果使用Random生成一个以时间为种子的随机数,事实上很容易通过上一个产生的随机数来推断下一个随机数。
1.2 SecureRandom
上面说到,random生成随机数时,若想生成真随机数,就要指定随机的种子。
现在看下SecureRandom产生随机数,一个简单的用法:

SecureRandom使用的种子,取决于系统默认的随机源是什么
系统默认的随机源取决于$JAVA_HOME/jre/lib/security/java.security配置中的securerandom.source属性。例如jdk1.8中该配置为:

使用无参构造函数实例化SecureRandom,在大多数系统中,默认的算法是“nativePRNG”(两种随机数算法,NativePRNG和SHA1PRNG。dev/[u]random两者之一就是NativePRNG,否则就是SHA1PRNG。),从/dev/random获取随机数。/dev/random是Linux操作系统的非物理真随机数产生设备接口。
虽然SecureRandom和Random都类似,如果种子一样,产生的随机数也都一样: 因为种子确定,随机数算法也确定,因此输出是确定的。
但是,SecureRandom类收集了一些随机事件,比如鼠标点击,键盘点击等等,SecureRandom 使用这些随机事件作为种子。这意味着,种子是不可预测的,而不像Random默认使用系统当前时间的毫秒数作为种子,有规律可寻。
总结:
不是安全场景的随机数,使用Random就好。
SecureRandom的由由于种子的随机性,更实用在安全场景下。
浙公网安备 33010602011771号