【问题标题】:Node: Generate 6 digits random number using crypto.randomBytes节点:使用 crypto.randomBytes 生成 6 位随机数
【发布时间】:2018-07-13 12:40:31
【问题描述】:

由于1000000 不是 2 的幂,从 0999999 随机生成精确值的正确方法是什么?

这是我的方法:

  1. 使用crypto.randomBytes生成3个字节并转换为hex
  2. 使用前 5 个字符转换为整数(最大值为 fffff == 1048575 > 999999
  3. 如果结果>999999,再次从步骤1开始

它会以某种方式创建一个递归函数。它在逻辑上是否正确,是否会引起性能问题?

【问题讨论】:

  • Math.floor(Math.random(1000000)*1000000)
  • 我知道,但math.random 不是加密安全的。我的问题是打算使用crypto.randomBytes
  • 您可以计算分数 YourRandInt/MaxPossible 并将其与您的预期范围 (999.999) 相乘,然后四舍五入到下一个整数。不确定这是否会更频繁地产生某些数字。
  • @TobiasK 谢谢,但是我认为这种方法与YourRandInt % 1000000 相同
  • 使用模有 x2 的值从 0 到 48575 的机会,我认为使用这种方法不会发生这种情况,因为每个 YourRandInt 在 0 和 1 之间都有一个唯一的分数。错误 I认为这可能发生的是你最终得到例如1.1 1.5 1.9 2.3 2.7 3.1 3.5 并且由于四舍五入得到2 3x 和3 仅2x。不过,我既不是数学家也不是密码专家,这只是我的一个想法,因为通常我喜欢 Math.random()*range 的想法,你只需要安全地生成分数。

标签: node.js algorithm cryptography


【解决方案1】:

有几种方法可以从随机位中提取一定范围内的随机数。一些常见的描述在NIST Special Publication 800-90A revision 1: Recommendation for Random Number Generation Using Deterministic Random Bit Generators

虽然该标准是关于确定性随机位生成的,但有一个有用的附录,称为 A.5 将随机位转换为随机数,其中描述了三种有用的方法。

描述的方法是:

  • A.5.1 简单丢弃方法
  • A.5.2 复杂丢弃方法
  • A.5.3 简单的模块化方法

它们中的前两个在运行时间方面不是确定性的,但会生成一个完全没有偏差的数字。它们基于拒绝抽样

复杂丢弃方法讨论了一种更优化的方案,用于在一定范围内生成大量随机数。我认为它对于几乎任何正常使用来说都太复杂了。如果您需要更高的效率,我会看看下面描述的 Optimized Simple Discard 方法。

简单模块化方法是时间常数和确定性的,但具有非零(但可忽略不计)偏差。但是,它需要相对大量的额外随机性才能实现可忽略的偏差;基本上,要获得 2^128 中的一个偏差,您需要在所需范围的位大小之上增加 128 位。对于较小的数字,这可能不是选择的方法。

您的算法显然是简单丢弃方法的一个版本(通常称为“拒绝采样”),所以没问题。


我自己想到了一种基于简单丢弃方法的非常有效的算法,称为"Optimized Simple Discard Method" or RNG-BC,其中“BC”代表“二进制比较”。它基于以下观察:比较只查看最高有效位,这意味着最低有效位仍应被视为随机,因此可以重复使用。请注意,此方法尚未经过官方同行评审;我确实提供了与简单丢弃方法等效的非正式证明。


当然,您应该使用一个通用方法,在给定N 的任何值的情况下,该方法都是有效的。在这种情况下,应该考虑复杂丢弃方法或简单模块化方法而不是简单丢弃方法。还有其他更复杂的算法效率更高,但通常使用这两种算法中的任何一种都可以。

请注意,在生成[0, N) 范围内的随机数时,首先检查N 是否为2 的幂通常是有益的。如果N 2 的幂,则无需使用任何这些可能昂贵的计算;只需使用随机位或字节生成器中所需的位即可。

【讨论】:

  • 请注意,您永远不必在计算中使用十六进制。十六进制只是位和字节的人类可读表示。所有计算最好在位和字节本身上执行。
  • 感谢您的精彩文章。就我而言,对于复杂的丢弃方法,999999^1/(2^20-1) ~ 0.95999999^2/(2^40-1) ~ 0.91 ... 所以最好的情况仍然是 t = 1`。如果我使用简单的模块化方法,我将需要为一个随机数使用 70 位!?听起来效率要低得多!你能解释一下按位计算吗?
  • 我知道一些按位运算符,如shifts 和逻辑and or,但它们与算法有何关系?
  • 在您的情况下,您很幸运,简单的丢弃方法效果很好。对于其他数字,可能需要在高达 50% 的情况下重新生成,特别是 0 到 2^X,其中第一位始终需要为 0。只需采用 X - 1 位即可避免这种特殊的讨厌。
【解决方案2】:

这是一个正确的算法 (https://en.wikipedia.org/wiki/Rejection_sampling),尽管您可以考虑使用按位运算而不是转换为十六进制。如果随机数生成器出现故障,它可以永远运行——您可以考虑尝试固定次数然后抛出异常而不是永远循环。

【讨论】:

    【解决方案3】:

    主要可能的性能问题是,在某些平台上,crypto.randomBytes 在熵用完时会阻塞。所以你在使用它时不想浪费任何随机性。

    因此,我将使用以下整数运算来代替您的字符串比较。

    if (random_bytes < 16700000) {
        return random_bytes = random_bytes - 100000 * Math.floor(random_bytes/100000);
    }
    

    这有大约 99.54% 的机会从前 3 个字节产生答案,而您的方法大约有 76% 的可能性。

    【讨论】:

    • @wdetac 糟糕,我错了。根据github.com/nodejs/node-v0.x-archive/issues/6372,它可以阻塞,但它在引擎盖下使用 /dev/urandom 仅在系统启动附近阻塞,而不是 /dev/random ,您可以比生成它更快地消耗熵。所以使用更少的随机字节不是问题。但是,如果您正在高速使用强大的加密源,您希望尽可能利用每一盎司的随机性。因此,如果可能,请以您愿意的方式进行猜测。
    【解决方案4】:

    我会建议以下方法:

    private generateCode(): string {
        let code: string = "";
    
        do {
            code += randomBytes(3).readUIntBE(0, 3);
            // code += Number.parseInt(randomBytes(3).toString("hex"), 16);
        } while (code.length < 6);
    
        return code.slice(0, 6);
    }
    

    这会将数字代码作为字符串返回,但如果需要将其作为数字获取,则更改为return Number.parseInt(code.slice(0, 6))

    【讨论】:

      【解决方案5】:

      我称之为random_6d 算法。最坏的情况只是一个额外的循环。

      var random_6d = function(n2){
          var n1 = crypto.randomBytes(3).readUIntLE(0, 3) >>> 4;
      
          if(n1 < 1000000)
              return n1;
      
          if(typeof n2 === 'undefined')
              return random_6d(n1);
      
          return Math.abs(n1 - n2);
      };
      

      循环版本:

      var random_6d = function(){
          var n1, n2;
      
          while(true){
              n1 = crypto.randomBytes(3).readUIntLE(0, 3) >>> 4;
      
              if(n1 < 1000000)
                  return n1;
      
              if(typeof n2 === 'undefined')
                  n2 = n1;
              else
                  return Math.abs(n1 - n2);
          };
      };
      

      【讨论】:

      • 这个循环最多只能运行两次,因此答案必须有偏差。
      • @MaartenBodewes 如何衡量这种偏见?
      猜你喜欢
      • 2018-12-21
      • 2016-02-10
      • 1970-01-01
      • 1970-01-01
      • 2014-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多