2017-01-03 361 views
3

我们一直使用的Math.random得到4000-64000之间:如何使用window.crypto.getRandomValues获得特定范围内的随机值

Math.floor(Math.random() * 60000 + 4000); 

我们必须现在替换这个随机数一个更加密码安全的随机数发生器。在搜索这个问题后,我们决定使用 window.crypto.getRandomValues。我无法弄清楚如何使用它来获得特定范围内的随机数。有人可以帮忙吗?

+0

另请参阅http://dimitri.xyz/random-ints-from-random-bits/和https://crypto.stackexchange.com/questions/8826/map-bytes-to-number和https:// crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-random-octet-string – caw

回答

5

对于给定的最小值和最大值,公式u \cdot \left (1 - {2^u \boldsymbol{\textup{mod}} (max-min) \over 2^u} \right) \sum_{i=0}^{\infty} \left(2^u \boldsymbol{\textup{mod}} (max-min) \over 2^u \right)^i (i + 1)描述了如果一次请求u位,平均使用多少位,如果返回结果会导致偏差,则重试。

幸运的是,最佳策略是简单地请求ceil(log2(max - min + 1))位。我们只能得到充分字节与crypto.getRandomValues反正,所以如果我们有每个函数调用的crypto.getRandomValues一个电话,我们能做的最好的是:

// Generate a random integer r with equal chance in min <= r < max. 
function randrange(min, max) { 
    var range = max - min; 
    if (range <= 0) { 
     throw new Exception('max must be larger than min'); 
    } 
    var requestBytes = Math.ceil(Math.log2(range)/8); 
    if (!requestBytes) { // No randomness required 
     return min; 
    } 
    var maxNum = Math.pow(256, requestBytes); 
    var ar = new Uint8Array(requestBytes); 

    while (true) { 
     window.crypto.getRandomValues(ar); 

     var val = 0; 
     for (var i = 0;i < requestBytes;i++) { 
      val = (val << 8) + ar[i]; 
     } 

     if (val < maxNum - maxNum % range) { 
      return min + (val % range); 
     } 
    } 
} 

如果产生许多值,你可以考虑一些优化,即请求提前多个字节(即更大的数组)。如果你的范围变小(比如说你想翻转一个硬币),那么比以比特为基础的方式工作也是有益的,例如,先前请求多个比特,然后只用尽你需要的随机比特。

+0

这真的很酷,但我注意到有点像'randrange(0,1)'无限循环' 。为什么? – Deele

+0

@Deele我很抱歉,如果'max - min == 1'有一个错误 - 之前的代码没有请求随机性,然后下一步没有任何意义。我添加了一个检查来捕获这个特殊情况。请注意''randrange(0,1)'总是返回0. – phihag

+0

您将如何增加数组大小(对于您的示例)以及大约应该在什么条件下执行? – Deele

相关问题