在Java开发中经常使用Random作为随机数的生成器,它能满足绝大多数随机数生成的需求,但是在安全性比较高的业务中要慎重使用。我们知道Random内部有一个seed属性,如果在构造方法中传入则使用传入的,否则会自动生成,seed对随机数的生成非常重要,如果两个Random的seed一样,则它们产生的随机数序列也是一样的。
由此可见,如果我们要想破解或者说预测随机数序列,只需要得到Random对象的seed属性即可,上面提到了seed属性如果不设置的话会自动生成,下面是Random默认的构造方法。
seedUniquifier方法会返回一个常数,而System.nanoTime()则是返回当前纳秒级别的时间戳,也就是说如果得到这个时间戳就能够得到seed值,但是这种风险还是比较小的。
Random不安全的主要是它的next方法,我们常用的nextInt,nextLong这些都是通过它生成的,next和nextInt的源码如下
从源码中可以看出,相邻的两个随机数之间是存在某种关系,首先是oldseed和nextseed存在线性关系,然后是seed和最终生成的int随机数也存在关系。
以nextInt为例,它最终生成的int随机数是通过nextSeed右移16位得到的,我们可以将int随机数左移16位得到一个基数baseSeed,由于nextSeed在右移过程中丢失了低16位,所以baseSeed<=nextSeed<=baseSeed + 216,再通过oldSeed和nextSeed的关系,我们就可以得到上次产生随机数的seed,核心代码如下。
下面是一个实例测试,我们可以通过Random先后得到两个int随机数,然后通过这两个随机数获取Random对象目前的seed属性值,从而推断出下一次将会产生的int随机数。
从上面可以看出,如果我们能够得到Random先后产生的两个随机数就能预测下一个随机数的值,从而引出安全问题,在对安全性要求比较高的业务场景中,我们可以使用SecureRandom来替代Random生成随机数,SecureRandom重写了next方法,即使两个SecureRandom的seed一样,它们产生的随机序列也是不一样的。