【问题标题】:Node.js - How to generate random numbers in specific range using crypto.randomBytesNode.js - 如何使用 crypto.randomBytes 生成特定范围内的随机数
【发布时间】:2016-02-10 02:46:08
【问题描述】:

如何使用 crypto.randomBytes 生成特定范围内的随机数?

我希望能够生成这样的随机数:

console.log(random(55, 956)); // where 55 is minimum and 956 is maximum

我只能在 random 函数中使用 crypto.randomBytes 来生成此范围的随机数。

我知道如何将生成的字节从 randomBytes 转换为十六进制或十进制,但我不知道如何从数学上从随机字节中获取特定范围内的随机数。

【问题讨论】:

  • 输出应该是整数还是浮点数?
  • @CodesInChaos 整数。
  • 相关:github.com/paragonie/random_compat 这是 PHP,但由于 PHP 对整数和浮点数的处理,如果你想正确实现它,你会遇到非常相似的问题。

标签: node.js math random cryptography


【解决方案1】:

要生成 [55 .. 956] 范围内的数字,您首先在 [0 .. 901] 范围内生成一个随机数,其中 901 = 956 - 55。然后将 55 添加到刚刚生成的数字上。

要生成 [0 .. 901] 范围内的数字,请选取两个随机字节并屏蔽 6 位。这将为您提供 [0 .. 1023] 范围内的 10 位随机数。如果该数字 不要尝试使用 MOD 将数字置于正确的范围内,否则会扭曲输出,使其非随机。

ETA:减少必须丢弃生成的数字的机会。

由于我们从 RNG 中获取两个字节,因此我们得到一个范围为 [0 .. 65535] 的数字。现在 65535 MOD 902 是 591。因此,如果我们的两字节随机数小于 (65535 - 591),即小于 64944,我们可以安全地使用 MOD 运算符,因为 [0 .. 901]现在同样可能。任何 >= 64944 的两字节数仍然必须丢弃,因为使用它会使输出偏离随机值。以前,必须拒绝一个数字的机会是 (1024 - 901) / 1024 = 12%。现在被拒绝的机会是 (65535 - 64944) / 65535 = 1%。我们不太可能不得不拒绝随机生成的数字。

running <- true
while running
  num <- two byte random
  if (num < 64944)
    result <- num MOD 902
    running <- false
  endif
endwhile
return result + 55

【讨论】:

  • 只是添加将字节转换为整数,您可以查看stackoverflow.com/questions/15821447/…中给出的答案
  • 你的意思是“MOD”的模运算吗?
  • 是的,除非您确切知道自己在做什么,否则不要使用它。 MOD后很容易得到非随机结果。
  • @rossum 感谢您的回答,但此方法不适用于生产级脚本。总是有可能连续几次获得超过 901 的数字……嗯,根据概率论,甚至有可能连续获得超过 901、百万或更多次的数字。我知道这不太可能,但即使连续 3 次,它也会长期影响服务器性能。
  • 您是否尝试在测试环境中运行它并查看问题实际发生的频率及其实际影响?过早的优化可能会导致比它解决的问题更多。
【解决方案2】:

要生成一定范围内的随机数,可以使用以下等式

Math.random() * (high - low) + low

但您想使用 crypto.randomBytes 而不是 Math.random() 此函数返回一个包含随机生成字节的缓冲区。反过来,您需要将此函数的结果从字节转换为十进制。这可以使用 biguint-format 包来完成。要安装此软件包,只需使用以下命令:

npm install biguint-format --save

现在您需要将crypto.randomBytes的结果转换为十进制,您可以这样做:

var x= crypto.randomBytes(1);
return format(x, 'dec');

现在您可以创建如下所示的随机函数:

var crypto = require('crypto'),
    format = require('biguint-format');

function randomC (qty) {
    var x= crypto.randomBytes(qty);
    return format(x, 'dec');
}
function random (low, high) {
    return randomC(4)/Math.pow(2,4*8-1) * (high - low) + low;
}
console.log(random(50,1000));

【讨论】:

  • @CodesInChaos:您的观点是有效的,但请注意,OP 只说他想使用 crypto.randomBytes,而不是说他希望将结果用于密码学。另外,您所指的“审查脚本”是什么?
  • @Mustafamg 谢谢!这正是我想要的。您应该在 randomC 前面添加 Math.round ,就是这样。 return Math.round(randomC(1)/256 * (high - low) + low);
  • @Mustafamg 如果您只生成几个字节,则无需使用 biguint-format npm。我们可以使用 parseInt(x.toString('hex'), 16) 代替 format(x, 'dec') 并避免添加额外的模块。
  • @Mustafamg 还有一件事......你不能只从一个字节中获得 50 到 1000 之间的所有可用数字。如果 high - low > 256,则 randomC(1)/256 是错误的。您应该使用 randomC(2)/65536 如果 high - low 和 randomC(3)/16777216 如果 high - low 等...如果 high - low 大于生成字节的十进制值,则无法从范围中获取所有可能的数字。
  • @Mustafamg 1) 以不同的概率选择不同的输出,即它们是有偏差的。如果范围大于 256,则根本不会选择许多可能的结果。 2)如果范围很大,最大值将是不可能的数字,因为你除以 256 而不是 255。3)Stackexchange 阻止发布以 -1 开头的 cmets,因为他们显然更喜欢人们在没有给出理由的情况下投票。
【解决方案3】:

感谢@Mustafamg 的回答和@CodesInChaos 的巨大帮助,我设法解决了这个问题。我做了一些调整并将范围增加到最大 256^6-1 或 281,474,976,710,655。范围可以增加更多,但您需要为大整数使用额外的库,因为 256^7-1 超出了 Number.MAX_SAFE_INTEGER 的限制。

如果有人有同样的问题,请随时使用它。

var crypto = require('crypto');

/*
Generating random numbers in specific range using crypto.randomBytes from crypto library
Maximum available range is 281474976710655 or 256^6-1
Maximum number for range must be equal or less than Number.MAX_SAFE_INTEGER (usually 9007199254740991)
Usage examples:
cryptoRandomNumber(0, 350);
cryptoRandomNumber(556, 1250425);
cryptoRandomNumber(0, 281474976710655);
cryptoRandomNumber((Number.MAX_SAFE_INTEGER-281474976710655), Number.MAX_SAFE_INTEGER);

Tested and working on 64bit Windows and Unix operation systems.
*/

function cryptoRandomNumber(minimum, maximum){
	var distance = maximum-minimum;
	
	if(minimum>=maximum){
		console.log('Minimum number should be less than maximum');
		return false;
	} else if(distance>281474976710655){
		console.log('You can not get all possible random numbers if range is greater than 256^6-1');
		return false;
	} else if(maximum>Number.MAX_SAFE_INTEGER){
		console.log('Maximum number should be safe integer limit');
		return false;
	} else {
		var maxBytes = 6;
		var maxDec = 281474976710656;
		
		// To avoid huge mathematical operations and increase function performance for small ranges, you can uncomment following script
		/*
		if(distance<256){
			maxBytes = 1;
			maxDec = 256;
		} else if(distance<65536){
			maxBytes = 2;
			maxDec = 65536;
		} else if(distance<16777216){
			maxBytes = 3;
			maxDec = 16777216;
		} else if(distance<4294967296){
			maxBytes = 4;
			maxDec = 4294967296;
		} else if(distance<1099511627776){
			maxBytes = 4;
			maxDec = 1099511627776;
		}
		*/
		
		var randbytes = parseInt(crypto.randomBytes(maxBytes).toString('hex'), 16);
		var result = Math.floor(randbytes/maxDec*(maximum-minimum+1)+minimum);
		
		if(result>maximum){
			result = maximum;
		}
		return result;
	}
}

到目前为止,它运行良好,您可以将它用作非常好的随机数生成器,但我严格不建议将此功能用于任何加密服务。如果愿意,请自行承担使用它的风险。

欢迎所有的 cmets、推荐和批评!

【讨论】:

  • 尝试cryptoRandomNumber(-1,+1) 并检查每个结果的共同点。我希望它是 1/4 | 1/2 | 1/4 而不是您可能期望的统一的 1/3。
  • @CodesInChaos 感谢您的建议,您是对的。这是来自 100 万个请求的结果:-1x249836、0x501146、1x249018。这适用于我尝试过的所有组合,不仅是从 -1 到 1 的范围 :( 知道如何修复它吗?
  • 如果你乘以 max-min+1 并截断结果而不是舍入,它在数学上是正确的,但要确保浮点舍入有时不会(非常很少)返回max+1 可能有点烦人。由于您的代码不使用双精度的完整 53 位,这应该是不可能的,但我个人宁愿只使用整数算术编写代码。
  • @CodesInChaos 非常感谢!有用!经过数百万次测试请求后,我从未遇到过 return max+1 的任何问题,但我添加了额外的代码以在发生这种情况时返回 false 并且我可以检测到并重试。所有潜在的数字现在都一样普遍。
  • 我宁愿添加 if(result&gt;max) result=max 之类的东西,而不是强迫调用者处理它。
【解决方案4】:

因此,大多数其他解决方案的问题在于它们会扭曲分布(您可能希望分布均匀)。

@rossum 的伪代码缺乏概括性。 (但他在文中提出了正确的解决方案)

// Generates a random integer in range [min, max]
function randomRange(min, max) {
    const diff = max - min + 1;

    // finds the minimum number of bit required to represent the diff
    const numberBit = Math.ceil(Math.log2(diff));
    // as we are limited to draw bytes, minimum number of bytes
    const numberBytes = Math.ceil(numberBit / 4);

    // as we might draw more bits than required, we look only at what we need (discard the rest)
    const mask = (1 << numberBit) - 1;

    let randomNumber;

    do {
        randomNumber = crypto.randomBytes(numberBytes).readUIntBE(0, numberBytes);
        randomNumber = randomNumber & mask;
    // number of bit might represent a numbers bigger than the diff, in that case try again
    } while (randomNumber >= diff);

    return randomNumber + min;
}

关于性能问题,基本上这个数字在 50% - 100% 的时间范围内是正确的(取决于参数)。也就是说,在最坏的情况下,循环执行 7 次以上,机会不到 1%,实际上,大多数情况下循环执行一到两次。

random-js 库承认大多数解决方案不提供具有均匀分布的随机数并提供更完整的解决方案

【讨论】:

    【解决方案5】:

    crypto 包现在有一个randomInt() 函数。它是在 v14.10.0 和 v12.19.0 中添加的。

    console.log(crypto.randomInt(55, 957)); // where 55 is minimum and 956 is maximum
    

    上限是排他的。

    这是(删节的)实现:

    // Largest integer we can read from a buffer.
    // e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6);
    const RAND_MAX = 0xFFFF_FFFF_FFFF;
    
    const range = max - min;
    
    const excess = RAND_MAX % range;
    const randLimit = RAND_MAX - excess;
    
    while (true) {
      const x = randomBytes(6).readUIntBE(0, 6);
      // If x > (maxVal - (maxVal % range)), we will get "modulo bias"
      if (x > randLimit) {
        // Try again
        continue;
      }
      const n = (x % range) + min;
      return n;
    }
    

    请参阅the full sourcethe official docs 了解更多信息。

    【讨论】:

      猜你喜欢
      • 2016-05-04
      • 2015-04-08
      • 1970-01-01
      • 2014-10-23
      • 2018-07-22
      • 1970-01-01
      • 2022-07-14
      相关资源
      最近更新 更多