243,583,606,221,817,150,598,111,409x plus d'entropie
Je recommanderais d'utiliser crypto.randomBytes . Ce n'est pas le cas sha1
, mais pour des raisons d'identification, c'est plus rapide et tout aussi "aléatoire".
var id = crypto.randomBytes(20).toString('hex');
//=> f26d60305dae929ef8640a75e70dd78ab809cfe9
La chaîne résultante sera deux fois plus longue que les octets aléatoires que vous générez; chaque octet codé en hexadécimal est de 2 caractères. 20 octets seront 40 caractères hexadécimaux.
En utilisant 20 octets, nous avons 256^20
ou 1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976 valeurs de sortie uniques. Ceci est identique aux sorties possibles de 160 bits (20 octets) de SHA1.
Sachant cela, shasum
nos octets aléatoires ne sont pas vraiment significatifs pour nous . C'est comme lancer un dé deux fois mais n'accepter que le deuxième lancer; quoi qu'il arrive, vous avez 6 résultats possibles à chaque jet, donc le premier jet est suffisant.
Pourquoi est-ce mieux?
Pour comprendre pourquoi c'est mieux, nous devons d'abord comprendre comment fonctionnent les fonctions de hachage. Les fonctions de hachage (y compris SHA1) généreront toujours la même sortie si la même entrée est donnée.
Disons que nous voulons générer des identifiants, mais notre entrée aléatoire est générée par un tirage au sort. Nous avons "heads"
ou"tails"
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
Si "heads"
revient, la sortie SHA1 sera la même que la première fois
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
Ok, donc un tirage au sort n'est pas un bon générateur d'ID aléatoires car nous n'avons que 2 sorties possibles.
Si nous utilisons une matrice standard à 6 faces, nous avons 6 entrées possibles. Devinez combien de sorties SHA1 possibles? 6!
input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278
Il est facile de se leurrer en pensant simplement parce que la sortie de notre fonction semble très aléatoire, qu'elle est très aléatoire.
Nous sommes tous les deux d'accord qu'un tirage au sort ou un dé à 6 faces ferait un mauvais générateur d'identifiant aléatoire, car nos résultats SHA1 possibles (la valeur que nous utilisons pour l'identifiant) sont très peu nombreux. Mais que faire si nous utilisons quelque chose qui a beaucoup plus de sorties? Comme un horodatage avec des millisecondes? Ou JavaScript Math.random
? Ou même une combinaison de ces deux?!
Calculons le nombre d'identifiants uniques que nous obtiendrions ...
L'unicité d'un horodatage en millisecondes
Lors de l'utilisation (new Date()).valueOf().toString()
, vous obtenez un numéro à 13 caractères (par exemple, 1375369309741
). Cependant, comme il s'agit d'un nombre de mise à jour séquentielle (une fois par milliseconde), les sorties sont presque toujours les mêmes. Nous allons jeter un coup d'oeil
for (var i=0; i<10; i++) {
console.log((new Date()).valueOf().toString());
}
console.log("OMG so not random");
// 1375369431838
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431840
// 1375369431840
// OMG so not random
Pour être juste, à des fins de comparaison, dans une minute donnée (un temps d'exécution d'opération généreux), vous aurez 60*1000
ou 60000
uniques.
Le caractère unique de Math.random
Maintenant, lors de l'utilisation Math.random
, en raison de la façon dont JavaScript représente les nombres à virgule flottante 64 bits, vous obtiendrez un nombre d'une longueur comprise entre 13 et 24 caractères. Un résultat plus long signifie plus de chiffres, ce qui signifie plus d'entropie. Tout d'abord, nous devons savoir quelle est la longueur la plus probable.
Le script ci-dessous déterminera la longueur la plus probable. Nous faisons cela en générant 1 million de nombres aléatoires et en incrémentant un compteur basé sur le .length
de chaque nombre.
// get distribution
var counts = [], rand, len;
for (var i=0; i<1000000; i++) {
rand = Math.random();
len = String(rand).length;
if (counts[len] === undefined) counts[len] = 0;
counts[len] += 1;
}
// calculate % frequency
var freq = counts.map(function(n) { return n/1000000 *100 });
En divisant chaque compteur par 1 million, nous obtenons la probabilité de la longueur du nombre retourné Math.random
.
len frequency(%)
------------------
13 0.0004
14 0.0066
15 0.0654
16 0.6768
17 6.6703
18 61.133 <- highest probability
19 28.089 <- second highest probability
20 3.0287
21 0.2989
22 0.0262
23 0.0040
24 0.0004
Donc, même si ce n'est pas tout à fait vrai, soyons généreux et disons que vous obtenez une sortie aléatoire de 19 caractères; 0.1234567890123456789
. Les premiers personnages seront toujours 0
et .
, donc en réalité, nous n'obtenons que 17 caractères aléatoires. Cela nous laisse avec 10^17
+1
(pour possible 0
; voir les notes ci-dessous) ou 100 000 000 000 000 001 uniques.
Alors, combien d'entrées aléatoires pouvons-nous générer?
Ok, nous avons calculé le nombre de résultats pour un horodatage en millisecondes et Math.random
100,000,000,000,000,001 (Math.random)
* 60,000 (timestamp)
-----------------------------
6,000,000,000,000,000,060,000
C'est un seul dé à 6 000 000 000 000 000 060 000 faces. Ou, pour rendre ce nombre plus digestible humainement, c'est à peu près le même nombre que
input outputs
------------------------------------------------------------------------------
( 1×) 6,000,000,000,000,000,060,000-sided die 6,000,000,000,000,000,060,000
(28×) 6-sided die 6,140,942,214,464,815,497,21
(72×) 2-sided coins 4,722,366,482,869,645,213,696
Ça sonne plutôt bien, non? Eh bien, découvrons ...
SHA1 produit une valeur de 20 octets, avec un possible 256 ^ 20 résultats. Nous n'utilisons donc vraiment pas SHA1 à son plein potentiel. Eh bien, combien utilisons-nous?
node> 6000000000000000060000 / Math.pow(256,20) * 100
Un horodatage à la milliseconde et Math.random n'utilise que 4,11e-27% du potentiel 160 bits de SHA1!
generator sha1 potential used
-----------------------------------------------------------------------------
crypto.randomBytes(20) 100%
Date() + Math.random() 0.00000000000000000000000000411%
6-sided die 0.000000000000000000000000000000000000000000000411%
A coin 0.000000000000000000000000000000000000000000000137%
Saints chats, mec! Regardez tous ces zéros. Alors, à quel point c'est mieux crypto.randomBytes(20)
? 243,583,606,221,817,150,598,111,409 fois mieux.
Remarques sur la +1
fréquence et la fréquence des zéros
Si vous vous interrogez sur le +1
, il est possible Math.random
de renvoyer un, 0
ce qui signifie qu'il y a 1 autre résultat unique possible que nous devons prendre en compte.
Sur la base de la discussion qui s'est déroulée ci-dessous, j'étais curieux de savoir à quelle fréquence un 0
allait arriver. Voici un petit script random_zero.js
, j'ai fait pour obtenir des données
#!/usr/bin/env node
var count = 0;
while (Math.random() !== 0) count++;
console.log(count);
Ensuite, je l'ai exécuté en 4 threads (j'ai un processeur à 4 cœurs), en ajoutant la sortie à un fichier
$ yes | xargs -n 1 -P 4 node random_zero.js >> zeroes.txt
Il s'avère donc que ce 0
n'est pas si difficile à obtenir. Après l' enregistrement de 100 valeurs , la moyenne était
1 randoms sur 3,164,854,823 est un 0
Cool! Des recherches supplémentaires seraient nécessaires pour savoir si ce nombre correspond à une distribution uniforme de la Math.random
mise en œuvre de la v8