真正的随机数是使用物理现象产生的:比如掷钱币、掷骰子、使用电子元件的噪音等等。这样的随机数发生器叫做物理性随机数发生器,它们的缺点是技术要求比较高,难以实现。
在实际应用中往往使用伪随机数就足够了。这些数列“似乎”是随机的,实际上它们是通过一个固定的、可以重复的计算方法产生的。计算机产生的随机数有很长的周期性。它们不真正地随机,因为它们实际上是可以计算出来的,但是它们具有类似于随机数的统计特征。这样的发生器叫做伪随机数发生器。
伪随机数又有强弱之分。强伪随机数一般是指相对难以猜解的随机数,比如服务器占用的内存数量作为随机数;而弱伪随机数是指相对容易猜解的随机数呢,典型的例子是当前的时间戳等。
C、C++、Java等程序语言中都有对应的随机数生成函数或者类。以我们最常用的Java语言为例,强伪随机数实现类是java.security.SecureRandom,该类使用临时文件夹的大小,线程休眠时间等值作为随机数种子;而弱伪随机数实现有java.util.Random类,默认使用当前时间作为种子,并且采用线性同余法计算下一个随机数。
Random r = new Random(5000); //5000作为seed,默认使用当前时间作为seed
for (int i=0;i<5;++i)
{
System.out.println(r.nextInt());
}
以上这段代码,无论你怎么跑都会打印出:
368121068
1992261337
-666891763
1167386397
1115095372
这么一个稳定的结果。这就是由于线性同余法带来的后果。那么,在我们的程序,如果使用Random类产品随机数,事实上很容易通过上一个产生的随机来推断下一个随机数。
伪随机数的应用里,验证码是另外一种典型应用。对于安全而言,验证码是一个非常有效的保护机制和人机区分机制,可以保障口令不被暴力破解,可以防止刷票,刷屏,重复提交恶意数据等。这里有一个很好的反面教材:
http://www.wooyun.org/bugs/wooyun-2010-07413
除了作为验证码之外,类似的应用还存在于一些活动的优惠券或者兑换码,如果兑换码设计不当,很容易被破解而破坏活动的公平性。
总结一下,使用随机数的场景需要注意以下几点
不要使用时间戳作为随机数
保证不同用处的随机数使用不同的种子
对于安全性要求高的随机数,使用强伪随机数产生器