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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33609404/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 19:30:14  来源:igfitidea点击:

Node.js - How to generate random numbers in specific range using crypto.randomBytes

node.jsmathrandomcryptography

提问by Nicolo

How can I generate random numbers in a specific range using crypto.randomBytes?

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

I want to be able to generate a random number like this:

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

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

and I'm limited to use crypto.randomBytesonly inside randomfunction to generate random number for this range.

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

I know how to convert generated bytes from randomBytes to hex or decimal but I can't figure out how to get a random number in a specific range from random bytes mathematically.

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

采纳答案by Nicolo

Thanks to answer from @Mustafamg and huge help from @CodesInChaos I managed to resolve this issue. I made some tweaks and increase range to maximum 256^6-1 or 281,474,976,710,655. Range can be increased more but you need to use additional library for big integers, because 256^7-1 is out of Number.MAX_SAFE_INTEGER limits.

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

If anyone have same problem feel free to use it.

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

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

So far it works fine and you can use it as really good random number generator, but I strictly not recommending using this function for any cryptographic services. If you will, use it on your own risk.

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

All comments, recommendations and critics are welcome!

欢迎所有评论、推荐和批评!

回答by Mustafamg

To generate random number in a certain range you can use the following equation

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

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

But you want to use crypto.randomBytes instead of Math.random() this function returns a buffer with randomly generated bytes. In turn, you need to convert the result of this function from bytes to decimal. this can be done using biguint-format package. To install this package simply use the following command:

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

npm install biguint-format --save

Now you need to convert the result of crypto.randomBytes to decimal, you can do that as follow:

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

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

Now you can create your random function which will be as follow:

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

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

回答by rossum

To generate numbers in the range [55 .. 956], you first generate a random number in the range [0 .. 901] where 901 = 956 - 55. Then add 55 to the number you just generated.

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

To generate a number in the range [0 .. 901], pick off two random bytes and mask off 6 bits. That will give you a 10 bit random number in the range [0 .. 1023]. If that number is <= 901 then you are finished. If it is bigger than 901, discard it and get two more random bytes. Do notattempt to use MOD, to get the number into the right range, that will distort the output making it non-random.

要生成 [0 .. 901] 范围内的数字,请选取两个随机字节并屏蔽掉 6 位。这将为您提供范围 [0 .. 1023] 中的 10 位随机数。如果该数字 <= 901,那么您就完成了。如果它大于 901,则丢弃它并再获得两个随机字节。千万不能尝试使用MOD,得到数到正确的范围,这将扭曲的输出变成非随机的。

ETA: To reduce the chance of having to discard a generated number.

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

Since we are taking two bytes from the RNG, we get a number in the range [0 .. 65535]. Now 65535 MOD 902 is 591. Hence, if our two-byte random number is less than (65535 - 591), that is, less than 64944, we can safely use the MOD operator, since each number in the range [0 .. 901] is now equally likely. Any two-byte number >= 64944 will still have to be thrown away, as using it would distort the output away from random. Before, the chances of having to reject a number were (1024 - 901) / 1024 = 12%. Now the chances of a rejection are (65535 - 64944) / 65535 = 1%. We are far less likely to have to reject the randomly generated number.

由于我们从 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

回答by Sami

So the issue with most other solutions are that they distort the distribution (which you probably would like to be uniform).

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

The pseudocode from @rossum lacks generalization. (But he proposed the right solution in the text)

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

About performance concerns, basically the number is in the right range between 50% - 100% of the time (depending on the parameters). That is in the worst case scenario the loop is executed more than 7 times with less than 1% chance and practically, most of the time the loop is executed one or two times.

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

The random-jslibrary acknowledges that most solution out there don't provide random numbers with uniform distributions and provides a more complete solution

随机的js库承认,大多数的解决方案在那里不提供的随机数均匀分布,并提供了一个更完整的解决方案

回答by HymanYang

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