Comment générer hash aléatoire SHA1 à utiliser comme ID dans node.js?

J'utilise cette ligne pour générer un identifiant sha1 pour node.js:

crypto.createHash('sha1').digest('hex'); 

Le problème est qu'il renvoie le même identifiant à chaque fois.

Est-il possible de le faire générer un identifiant aléatoire à chaque fois afin que je puisse l'utiliser comme identifiant de document de base de données?

Jetez un oeil ici: Comment puis-je utiliser node.js Crypto pour créer un hachage HMAC-SHA1? Je créerais un hash de l'horodatage actuel + un nombre aléatoire pour assurer l'unicité du hash:

 var current_date = (new Date()).valueOf().toString(); var random = Math.random().toString(); crypto.createHash('sha1').update(current_date + random).digest('hex'); 

243 583 606,221,817,150,598,111,409x plus d'entropie

Je recommanderais l'utilisation de crypto.randomBytes . Ce n'est pas bon, mais à des fins d'identification, c'est plus rapide et tout simplement "aléatoire".

 var id = crypto.randomBytes(20).toString('hex'); //=> bb5dc8842ca31d4603d6aa11448d1654 

La chaîne résultante sera deux fois plus longue que les octets aléatoires générés; Chaque octet codé en hexadécimal est de 2 caractères. 20 octets seront 40 caractères d'hex.

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.

En sachant cela, il n'est pas vraiment utile pour nous de shasum nos octets aléatoires. C'est comme rouler un mort à deux reprises, mais seulement en acceptant le deuxième rouleau; Quoi qu'il en soit, vous avez 6 résultats possibles chaque rouleau, donc le premier rouleau 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 ID, mais notre apport aléatoire est généré par un jeu de pièces. Nous avons "heads" ou "tails"

 % echo -n "heads" | shasum c25dda249cdece9d908cc33adcd16aa05e20290f - % echo -n "tails" | shasum 71ac9eed6a76a285ae035fe84a251d56ae9485a4 - 

Si "heads" surviennent, la sortie SHA1 sera la même que la première fois

 % echo -n "heads" | shasum c25dda249cdece9d908cc33adcd16aa05e20290f - 

D'accord, donc un jeu de pièces n'est pas un générateur d'ID aléatoire génial 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 nous tromper en pensant simplement que la sortie de notre fonction semble très aléatoire, qu'elle est très aléatoire.

Nous reconnaissons tous deux qu'un jet de monnaie ou un décompte à 6 côtés produirait un mauvais générateur d'identificateurs aléatoires, car nos résultats possibles de SHA1 (la valeur que nous utilisons pour l'ID) sont très peu nombreux. Mais que faire si nous utilisons quelque chose qui a beaucoup plus de résultats? Comme un timestamp avec des millisecondes? Ou Math.random de JavaScript? Ou même une combinaison de ces deux ?!

Calculons le nombre d'identifiants uniques que nous aurions …


L'unicité d'un horodatage avec des millisecondes

Lors de l'utilisation (new Date()).valueOf().toString() , vous obtenez un numéro de 13 caractères (par exemple, 1375369309741 ). Cependant, étant donné qu'il s'agit d'un numéro 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 de l'opération généreuse), vous aurez 60*1000 ou 60000 uniques.


L'unicité de Math.random

Maintenant, lorsque vous utilisez Math.random , en raison de la façon dont JavaScript représente les nombres à virgule flottante à 64 bits, vous obtiendrez un nombre avec une longueur comprise entre 13 et 24 caractères. Un résultat plus long signifie plus de chiffres qui signifie plus d'entropie. Tout d'abord, nous devons déterminer quelle est la longueur la plus probable.

Le script ci-dessous déterminera quelle longueur est la plus probable. Nous faisons cela en générant 1 million de nombres aléatoires et en incrémentant un compteur en fonction de la .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 de 1 million, on obtient la probabilité que la longueur du nombre soit revenue de 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 caractères seront toujours 0 et . , Donc nous n'obtenons que 17 caractères aléatoires. Cela nous laisse 10^17 +1 (pour le possible 0 , voir les notes ci-dessous) ou 100,000,000,000,000,001 uniques.


Donc, combien d'entrées aléatoires pouvons-nous générer?

D'accord, nous avons calculé le nombre de résultats pour un timestamp de milliseconde et Math.random

  100,000,000,000,000,001 (Math.random) * 60,000 (timestamp) ----------------------------- 6,000,000,000,000,000,060,000 

C'est une seule mort de 6 000 000 000 000 000 000 000 000. Ou, pour rendre ce nombre plus humainement digestible, il s'agit à peu près du 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 

Cela semble très bien, n'est-ce pas? Eh bien, découvrons …

SHA1 produit une valeur de 20 octets, avec des résultats possibles de 256 ^ 20. Donc, nous n'utilisons pas vraiment le potentiel complet de SHA1. Eh bien, combien utilisons-nous?

 node> 6000000000000000060000 / Math.pow(256,20) * 100 

Un timestamp de milliseconde et Math.random utilise seulement 4.11e-27 pour cent du potentiel de 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% 

Chats sacrés, mec! Regardez tous ces zéros. Alors, combien mieux est crypto.randomBytes(20) ? 243 583 606,221,817,150,598,111,409 fois mieux.


Remarques sur le +1 et la fréquence des zéros

Si vous vous demandez le +1 , il est possible pour Math.random de renvoyer un 0 ce qui signifie qu'il y a 1 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 la fréquence d'un 0 . 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-core), en ajoutant la sortie à un fichier

 $ yes | xargs -n 1 -P 4 node random_zero.js >> zeroes.txt 

Il s'avère donc qu'un 0 n'est pas si difficile à obtenir. Après avoir enregistré 100 valeurs , la moyenne était

1 sur 3 164 854 823 randoms est un 0

Cool! Davantage de recherche serait nécessaire pour savoir si ce nombre est à la hauteur d'une répartition uniforme de la mise en œuvre Math.random de v8

Faites-le dans le navigateur aussi!

EDIT: cela ne correspondait pas vraiment au flux de ma réponse précédente. Je le laisse ici comme une deuxième réponse pour les gens qui pourraient chercher à le faire dans le navigateur.

Vous pouvez faire ce côté client dans les navigateurs modernes, si vous le souhaitez

 // str byteToHex(uint8 byte) // converts a single byte to a hex string function byteToHex(byte) { return ('0' + byte.toString(16)).slice(-2); } // str generateId(int len); // len - must be an even number (default: 40) function generateId(len) { var arr = new Uint8Array((len || 40) / 2); window.crypto.getRandomValues(arr); return [].map.call(arr, byteToHex).join(""); } 

D'accord, vérifions-le!

 generateId(); // "1e6ef8d5c851a3b5c5ad78f96dd086e4a77da800" generateId(20); // "d2180620d8f781178840" 

Exigences du navigateur

 Browser Minimum Version -------------------------- Chrome 11.0 Firefox 21.0 IE 11.0 Opera 15.0 Safari 5.1