zoukankan      html  css  js  c++  java
  • java随机数之Random和SecureRandom

    一、前言

      在一次项目的安全测试源代码扫描中,报由random()实施的随机数生成器不能抵挡加密攻击。其中报漏洞的源代码如下:

    int number = (int) ((Math.random() * 9 + 1) * Math.pow(10, 6 -1));
    String numStr = String.valueOf(number);

       其中的报漏洞的解释是这样说的,在对安全性要求较高的环境中,使用能够生成可预测值的函数作为伪随机数据源,会产生Insecure Randomness(不安全随机性)错误。电脑是一种具有确定性的机器,因此不可能产生真正的随机性,伪随机数生成器(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的实现方法。
      Sun 在名为 SHA1PRNG 的 Java 版本中提供了一种单独实现 SecureRandom 的方式, Sun 将其描述为计算:
    “SHA-1 可以计算一个真实的随机种子参数的散列值, 同时, 该种子参数带有一个 64 比特的计算器, 会在每一次操作后加 1。 在 160 比特的 SHA-1 输出中, 只能使用64 比特的输出 1。 ”
    然而, 文档中有关 Sun 的 SHA1PRNG 算法实现细节的相关记录很少, 人们无法了解算法实现中使用的熵的来源, 因此也并不清楚输出中到底存在多少真实的随机数值。尽管有关 Sun 的实现方法网络上有各种各样的猜测, 但是有一点无庸置疑, 即算法具有很强的加密性, 可以在对安全性极为敏感的各种内容中安全地使用。

    三、使用:

    案例1:

    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

    案例2:获取随机字符串,参考微信(wxpay-sdk,java_sdk_v3.0.9)

    import java.security.SecureRandom;
    import java.util.Random;
    public class WXPayUtil {
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final Random RANDOM = new SecureRandom();
    /**
    * 获取随机字符串 Nonce Str
    *
    * @return String 随机字符串
    */
    public static String generateNonceStr() {
    char[] nonceChars = new char[32];
    for (int index = 0; index < nonceChars.length; ++index) {
    nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
    }
    return new String(nonceChars);
    }

    public static void main(String[] args) {
    System.out.println(WXPayUtil.generateNonceStr());
    }
    }
    // 结果
    // oIjXt5Y9g2drzfzaR4hF9haaiDjSVDCX

     四、常用随机数的一些汇总

      对于像Math.random()等实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数,在种子数的基础上进行一定的交换,从而产生需要的随机数字。在实际的项目开发过程中,经常需要产生一些随机数值,例如网站登录中的校验数字等,或者需要以一定的几率实现某种效果,游戏程序中的物品掉落,抽奖程序等。

    1. Math.random() 静态方法,产生的随机数是 0 - 1 之间的一个 double,即 0 <= random <= 1.

    缺点:结果可预测

    举例:

    for (int i = 0; i < 10; i++) {
      System.out.println(Math.random());
    }
    0.3598613895606426
    0.2666778145365811
    0.25090731064243355
    0.011064998061666276
    0.600686228175639
    0.9084006027629496
    0.12700524654847833
    0.6084605849069343
    0.7290804782514261
    0.9923831908303121

    实现原理:

    When this method is first called, it creates a single new pseudorandom-number generator, exactly as if by the expression new java.util.Random()
    This new pseudorandom-number generator is used thereafter for all calls to this method and is used nowhere else.

    当第一次调用 Math.random() 方法时,自动创建了一个伪随机数生成器,实际上用的是 new java.util.Random()。当接下来继续调用 Math.random() 方法时,就会使用这个新的伪随机数生成器。

    源码如下:

    public static double random() {
        Random rnd = randomNumberGenerator;
        if (rnd == null) rnd = initRNG(); // 第一次调用,创建一个伪随机数生成器
        return rnd.nextDouble();
    }
    private static synchronized Random initRNG() {
        Random rnd = randomNumberGenerator;
        return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd; // 实际上用的是new java.util.Random()
    }
    This method is properly synchronized to allow correct use by more than one thread. However, 
    if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.

    initRNG() 方法是 synchronized 的,因此在多线程情况下,只有一个线程会负责创建伪随机数生成器(使用当前时间作为种子),其他线程则利用该伪随机数生成器产生随机数。

    因此 Math.random() 方法是线程安全的。

    什么情况下随机数的生成线程不安全:

    • 线程1在第一次调用 random() 时产生一个生成器 generator1,使用当前时间作为种子。
    • 线程2在第一次调用 random() 时产生一个生成器 generator2,使用当前时间作为种子。
    • 碰巧 generator1 和 generator2 使用相同的种子,导致 generator1 以后产生的随机数每次都和 generator2 以后产生的随机数相同。

    什么情况下随机数的生成线程安全: Math.random() 静态方法使用

    • 线程1在第一次调用 random() 时产生一个生成器 generator1,使用当前时间作为种子。
    • 线程2在第一次调用 random() 时发现已经有一个生成器 generator1,则直接使用生成器 generator1
    public class JavaRandom {
        public static void main(String args[]) {
            new MyThread().start();
            new MyThread().start();
        }
    }
    class MyThread extends Thread {
        public void run() {
            for (int i = 0; i < 2; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + Math.random());
            }
        }
    }

    结果:

    Thread-1: 0.8043581595645333
    Thread-0: 0.9338269554390357
    Thread-1: 0.5571569413128877
    Thread-0: 0.37484586843392464

     2. 对于其它的一些随机数相关的工具类可参考java.util.Random 工具类、java.util.concurrent.ThreadLocalRandom 工具类、java.Security.SecureRandom、随机字符串.(https://www.jianshu.com/p/2f6acd169202)

    参考来源:

    作者:专职跑龙套
    链接:https://www.jianshu.com/p/2f6acd169202
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 相关阅读:
    人肉搜索利大于弊?
    上海美容美发连锁店疯狂扩展,大面值会员卡慎办
    jQuery简单入门
    把苦难装在心里《赢在中国》(20080527)
    赢在中国(080304)
    ASP.NET MVC Preview 2新特性
    Lucifer的一场暴强围英雄表演
    php pack、unpack、ord 函数使用方法(二进制流接口应用实例)
    PHP开发绝对不能违背的安全铁则(摘)
    MySQL数据类型:UNSIGNED注意事项
  • 原文地址:https://www.cnblogs.com/damoblog/p/14341018.html
Copyright © 2011-2022 走看看