【问题标题】:How to use window.crypto.getRandomValues to get random values in a specific range如何使用 window.crypto.getRandomValues 获取特定范围内的随机值
【发布时间】:2017-01-03 06:15:09
【问题描述】:

我们一直在使用 Math.random 来获取 4000-64000 之间的随机数。:

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

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

【问题讨论】:

标签: javascript java random


【解决方案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);
        }
    }
}

如果你生成很多值,你可以考虑一些优化,即提前请求更多的字节(即更大的数组)。如果你的范围变得更小(比如你想掷硬币),那么以基于位的方式工作也可能是有益的,即预先请求许多位,然后只用完你真正需要的随机位。

【讨论】:

  • @Deele 这个答案中的代码会自动缩放数组大小。如果您生成大量随机数并且范围非常小,则请求较大块的随机性并更仔细地提取它可能会很有用。很难确定具体数字,但我会开始优化,比如每秒 100 个请求,或者如果你只是抛硬币,则为 10/s。
  • @caw 我认为你有很多不同之处。由于 JavaScript 解释器并不总是实现尾递归调用,递归解决方案可能会遇到堆栈边界。递归通常也较慢,因为没有优化就分配了堆栈帧。 diceware.js 解决方案还会重做每个递归步骤中的所有计算,这是不必要的。通过使用身份2^8x = 256^x,我的解决方案保存了一个变量和几条指令。他们的代码平均需要更多尝试:对于 min=1 max=3,他们有 1/4 的机会必须重试。我的代码只在 1/256 重试。
  • 特别是,最终条件val + range - (val % range) &lt; maxNumval &lt; (Math.floor(maxNum / range) * range) 是如何等价的?我看到它是具有扩展范围的拒绝采样,这必须是我们可以安全地应用模而不引入偏差的条件。但我看不出这些条件是如何等价的。抱歉,这是最后一条评论! ;)
  • @caw 据我了解他们的README,OpenWireless 项目已经死了。您的建议(或其更简单的等价物val &lt; maxNum - maxNum % range)是正确的。我最初的检查没有引入抽样偏差,而是丢弃了可以采取的结果(例如val=255 range=1 maxNum=256。我已经更新了答案。Here is the test code to verify it is correct
  • 此答案中的函数将max 排除在范围之外,但包括min。要解决此问题,请将 var range = max - min; 更改为 var range = max - min + 1;
【解决方案2】:

Math.random 的简单替换可能如下所示:

/**
 * Return values in the range of [0, 1)
 */
const randomFloat = function () {
  const int = window.crypto.getRandomValues(new Uint32Array(1))[0]
  return int / 2**32
}

将其扩展到整数:


/**
 * Return integers in the range of [min, max)
 *
 * @todo check that min is <= max.
 */
const randomInt = function (min, max) {
  const range = max - min
  return Math.floor(randomFloat() * range + min)
}

要将其扩展到整数数组:


/**
 * Generate an array of integers in the range of [min, max).
 */
const randomIntArray = function (length, min, max) {
  return new Array(length).fill(0).map(() => randomInt(min, max))
}

生成一个包含 0 到 2 的十个整数的数组:

randomIntArray(10, 0, 3)
[0, 2, 1, 2, 0, 0, 1, 0, 1, 0]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-16
    • 2012-07-29
    • 1970-01-01
    • 2018-08-20
    • 2020-10-23
    • 2016-05-31
    • 2019-07-03
    相关资源
    最近更新 更多