电脑是一种具有确定性的机器, 因此不可能产生真正的随机性。 伪随机数生成器 (PRNG) 近似于随机算法, 始于一个能计算后续数值的种子。PRNG 包括两种类型: 统计学的 PRNG 和密码学的 PRNG。 统计学的 PRNG 提供很多有用的统计属性, 但其输出结果很容易预测, 因此容易复制数值流。 在安全性所依
赖的生成值不可预测的情况下, 这种类型并不适用。 密码学的 PRNG 生成的输出结果较难预测, 可解决这一问题。
为保证值的加密安全性, 必须使攻击者根本无法、 或几乎不可能鉴别生成的随机值和真正的随机值。 通常情况下, 如果并未声明 PRNG 算法带有加密保护, 那么它很可能就是统计学的 PRNG, 因此不应在对安全性要求较高的环境中使用, 否则会导致严重的漏洞(如易于猜测的密码、 可预测的加密密钥、 Session Hijacking 和 DNS Spoofing) 。
示例: 下面的代码可利用统计学的 PRNG 为购买产品后仍在有效期内的收据创建一个URL。
String GenerateReceiptURL(String baseUrl) {
Random ranGen = new Random();
ranGen.setSeed((new Date()).getTime());
return (baseUrl + ranGen.nextInt(400000000) + ".html");
}
这段代码使用 Random.nextInt() 函数为它生成的收据页面生成“唯一”的标识符。
由于 Random.nextInt() 是统计学的 PRNG, 攻击者很容易猜到其生成的字符串。 尽管收据系统的底层设计并不完善, 但若使用不会生成可预测收据标识符的随机数生成器(如密码学的 PRNG) , 就会更安全些
解决方案
当不可预测性至关重要时, 如大多数对安全性要求较高的环境都采用随机性, 这时可以使用密码学的 PRNG。 不管选择了哪一种 PRNG, 都要始终使用带有充足熵的数值
作为该算法的种子。 (诸如当前时间之类的数值只提供很小的熵, 因此不应该使用。 )
Java 语言在 java.security.SecureRandom 中提供了一个加密 PRNG。 就像 java.security 中其他以算法为基础的类那样, SecureRandom 提供了与某
个特定算法集合相关的包, 该包可以独立实现。 当使用 SecureRandom.getInstance() 请求一个 SecureRandom 实例时, 您可以申请实现某个特定的算法。 如
果算法可行, 那么您可以将它作为 SecureRandom 的对象使用。 如果算法不可行, 或者您没有为算法明确特定的实现方法, 那么会由系统为您选择 SecureRandom果算法可行, 那么您可以将它作为 SecureRandom 的对象使用。 如果算法不可行, 或者您没有为算法明确特定的实现方法, 那么会由系统为您选择 SecureRandom的实现方法。
Sun 在名为 SHA1PRNG 的 Java 版本中提供了一种单独实现 SecureRandom 的方式, Sun 将其描述为计算:
“SHA-1 可以计算一个真实的随机种子参数的散列值, 同时, 该种子参数带有一个 64 比特的计算器, 会在每一次操作后加 1。 在 160 比特的 SHA-1 输出中, 只能使用64 比特的输出 1。 ”
然而, 文档中有关 Sun 的 SHA1PRNG 算法实现细节的相关记录很少, 人们无法了解算法实现中使用的熵的来源, 因此也并不清楚输出中到底存在多少真实的随机数值。
尽管有关 Sun 的实现方法网络上有各种各样的猜测, 但是有一点无庸置疑, 即算法具有很强的加密性, 可以在对安全性极为敏感的各种内容中安全地使用。
SecureRandom random1 = SecureRandom.getInstance("SHA1PRNG");
SecureRandom random2 = SecureRandom.getInstance("SHA1PRNG");
for (int i = 0; i < 5; i++) {
System.out.println(random1.nextInt() + " != " + random2.nextInt());
}
704046703 != 2117229935
60819811 != 107252259
425075610 != -295395347
682299589 != -1637998900
-1147654329 != 1418666937
本人测试产生六位随机数:
/**
* 产生6位随机数
* @return
*/
public static int generateSixRandomNum() {
try {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
int num = random.nextInt(900000) + 100000;
return num;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}