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

问题描述 投票:9回答:5

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

我希望能够像这样生成一个随机数:

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

我只能在随机函数内使用crypto.randomBytes来为这个范围生成随机数。

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

node.js math random cryptography
5个回答
18
投票

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

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

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

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));

5
投票

感谢@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;
	}
}

到目前为止它工作正常,你可以使用它作为真正好的随机数生成器,但我严格不建议使用此功能的任何加密服务。如果愿意,请自担风险使用。

欢迎所有评论,建议和评论家!


4
投票

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

要生成[0 .. 901]范围内的数字,请选取两个随机字节并屏蔽6位。这将为您提供[0 .. 1023]范围内的10位随机数。如果该数字<= 901,则表示您已完成。如果它大于901,则丢弃它并获得两个随机字节。不要尝试使用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

0
投票

所以大多数其他解决方案的问题是它们扭曲了分布(你可能希望统一)。

来自@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库承认大多数解决方案不提供具有统一分布的随机数,并提供更完整的解决方案


-1
投票

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;
	}
}
© www.soinside.com 2019 - 2024. All rights reserved.