web-dev-qa-db-fra.com

Node.js - Comment générer des nombres aléatoires dans une plage spécifique en utilisant crypto.randomBytes

Comment puis-je générer des nombres aléatoires dans une plage spécifique en utilisant crypto.randomBytes?

Je veux pouvoir générer un nombre aléatoire comme celui-ci:

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

et je suis limité à utiliser crypto.randomBytes uniquement à l'intérieur de la fonction random pour générer un nombre aléatoire pour cette plage.

Je sais comment convertir les octets générés de randomBytes en hexadécimal ou décimal, mais je ne peux pas comprendre comment obtenir un nombre aléatoire dans une plage spécifique à partir d'octets aléatoires mathématiquement.

10
Nicolo

Grâce à la réponse de @Mustafamg et à l'énorme aide de @CodesInChaos, j'ai réussi à résoudre ce problème. J'ai fait quelques ajustements et j'ai augmenté la plage jusqu'à 256 ^ 6-1 ou 281 474 976 710 655. La plage peut être augmentée davantage, mais vous devez utiliser une bibliothèque supplémentaire pour les grands entiers, car 256 ^ 7-1 est hors des limites de Number.MAX_SAFE_INTEGER.

Si quelqu'un a le même problème, n'hésitez pas à l'utiliser.

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

Jusqu'à présent, cela fonctionne bien et vous pouvez l'utiliser comme un très bon générateur de nombres aléatoires, mais je ne recommande strictement pas d'utiliser cette fonction pour les services cryptographiques. Si vous le souhaitez, utilisez-le à vos propres risques.

Tous les commentaires, recommandations et critiques sont les bienvenus!

4
Nicolo

Pour générer un nombre aléatoire dans une certaine plage, vous pouvez utiliser l'équation suivante

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

Mais vous voulez utiliser crypto.randomBytes au lieu de Math.random () cette fonction retourne un tampon avec des octets générés aléatoirement. À son tour, vous devez convertir le résultat de cette fonction d'octets en décimal. cela peut être fait en utilisant un package au format biguint. Pour installer ce package, utilisez simplement la commande suivante:

npm install biguint-format --save

Maintenant, vous devez convertir le résultat de crypto.randomBytes en décimal, vous pouvez le faire comme suit:

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

Vous pouvez maintenant créer votre fonction aléatoire qui sera la suivante:

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

Pour générer des nombres dans la plage [55 .. 956], vous générez d'abord un nombre aléatoire dans la plage [0 .. 901] où 901 = 956 - 55. Ajoutez ensuite 55 au nombre que vous venez de générer.

Pour générer un nombre dans la plage [0 .. 901], sélectionnez deux octets aléatoires et masquez 6 bits. Cela vous donnera un nombre aléatoire de 10 bits dans la plage [0 .. 1023]. Si ce nombre est <= 901, vous avez terminé. S'il est supérieur à 901, jetez-le et obtenez deux octets aléatoires supplémentaires. Faites pas essayez d'utiliser MOD, pour obtenir le nombre dans la bonne plage, ce qui faussera la sortie en la rendant non aléatoire.

ETA: pour réduire le risque d'avoir à supprimer un numéro généré.

Puisque nous prenons deux octets du RNG, nous obtenons un nombre dans la plage [0 .. 65535]. Maintenant, 65535 MOD 902 est 591. Par conséquent, si notre nombre aléatoire à deux octets est inférieur à (65535 - 591), c'est-à-dire inférieur à 64944, nous pouvons utiliser en toute sécurité l'opérateur MOD, car chaque nombre dans la plage [0 .. 901] est désormais tout aussi probable. Tout nombre à deux octets> = 64944 devra toujours être jeté, car son utilisation déformerait la sortie du hasard. Avant, les chances de devoir rejeter un numéro étaient (1024 - 901)/1024 = 12%. Maintenant, les chances de rejet sont (65535 - 64944)/65535 = 1%. Nous sommes beaucoup moins susceptibles d'avoir à rejeter le nombre généré de manière aléatoire.

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

Donc, le problème avec la plupart des autres solutions est qu'elles faussent la distribution (que vous aimeriez probablement être uniforme).

Le pseudocode de @rossum manque de généralisation. (Mais il a proposé la bonne solution dans le texte)

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

Concernant les problèmes de performances, le nombre se situe dans la bonne plage entre 50% et 100% du temps (en fonction des paramètres). Dans le pire des cas, la boucle est exécutée plus de 7 fois avec moins de 1% de chances et pratiquement la plupart du temps, la boucle est exécutée une ou deux fois.

La bibliothèque random-js reconnaît que la plupart des solutions ne fournissent pas de nombres aléatoires avec des distributions uniformes et fournit une solution plus complète

0
Sami