web-dev-qa-db-fra.com

Comment générer un hash SHA1 aléatoire à utiliser comme identifiant 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?

119
ajsie

Regardez ici: Comment utiliser node.js Crypto pour créer un hachage HMAC-SHA1? Je créerais un hachage de l'horodatage actuel + un nombre aléatoire pour en assurer l'unicité:

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

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

Je recommanderais d'utiliser crypto.randomBytes . Ce n'est pas sha1, Mais à des fins 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 encodé en hexadécimal est composé de 2 caractères. 20 octets auront 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 SHA1 sur 160 bits (20 octets).

Sachant cela, cela n’a pas vraiment de sens de shasum nos octets aléatoires. C'est comme lancer un dé deux fois mais n'accepter que le deuxième lancer; quoi qu'il en soit, vous avez 6 résultats possibles pour chaque rouleau, donc le premier rouleau est suffisant.


Pourquoi est-ce mieux?

Pour comprendre pourquoi c'est mieux, nous devons d'abord comprendre le fonctionnement des 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.

Supposons que nous voulions générer des identifiants mais que notre saisie aléatoire soit 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" Réapparaît, la sortie SHA1 sera la même que la première fois

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

Ok, un tirage au sort n’est donc pas un excellent générateur d’identité aléatoire, car nous n’avons que deux sorties possibles.

Si nous utilisons un dé 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 que le résultat de notre fonction semble très aléatoire, que cela est très aléatoire.

Nous sommes tous les deux d’accord pour dire qu’un tirage au sort ou un dé à 6 faces constituerait un mauvais générateur d’identités aléatoires, car nos résultats SHA1 possibles (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 horodatage avec millisecondes? Ou JavaScript Math.random? Ou même une combinaison combinaison de ces deux?!

Calculons combien d'identifiants uniques nous obtiendrions ...


Caractère unique d'un horodatage avec millisecondes

Lorsque vous utilisez (new Date()).valueOf().toString(), Vous obtenez un numéro à 13 caractères (par exemple, 1375369309741). Toutefois, comme il s’agit d’un numéro à jour séquentiel (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, en une minute donnée (un temps d'exécution généreux), 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 des nombres à virgule flottante 64 bits, vous obtenez un nombre dont la longueur varie de 13 à 24 caractères. Un résultat plus long signifie plus de chiffres, ce qui signifie plus d'entropie. Premièrement, 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 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é que la longueur du nombre retourné soit 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 ., Nous n'obtenons donc que 17 caractères aléatoires. Cela nous laisse avec 10^17+1 (Pour un possible 0; Voir 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 de la 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 un seul dé 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 bien, non? Eh bien, découvrons ...

SHA1 produit une valeur de 20 octets, avec un maximum de 256 ^ 20 résultats. Nous n'utilisons donc pas vraiment SHA1 à son plein potentiel. Eh bien, combien en utilisons-nous?

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

Un timestamp d'une milliseconde et Math.random utilise seulement 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%

Des chats sacrés, mec! Regardez tous ces zéros. Alors, comment va mieux crypto.randomBytes(20)? 243,583,606,221,817,150,598,111,409 mieux.


Notes sur le +1 Et la fréquence des zéros

Si vous vous interrogez sur le paramètre +1, Il est possible pour Math.random De renvoyer un 0, Ce qui signifie qu'il y a 1 résultat unique supplémentaire possible à prendre en compte.

Sur la base de la discussion qui a eu lieu ci-dessous, j'étais curieux de savoir la fréquence à laquelle un 0 Serait affiché. 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é dans 4 threads (j'ai un processeur 4-core), en ajoutant la sortie dans un fichier

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

Donc, il s'avère qu'un 0 N'est pas si difficile à obtenir. Après 100 valeurs ont été enregistrées, la moyenne était

1 in 3 164 854 823 randoms est un 0

Cool! Davantage de recherche serait nécessaire pour savoir si ce nombre correspond à une distribution uniforme de la mise en œuvre de v8 Math.random

577
user633183

Faites-le aussi dans le navigateur!

EDIT: cela ne correspond pas vraiment au flux de ma réponse précédente. Je le laisse ici comme deuxième réponse pour les personnes qui pourraient vouloir 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 = 40) {
  var arr = new Uint8Array(len / 2);
  window.crypto.getRandomValues(arr);
  return Array.from(arr, byteToHex).join("");
}

console.log(generateId())
// "1e6ef8d5c851a3b5c5ad78f96dd086e4a77da800"

console.log(generateId(20))
// "d2180620d8f781178840"

Configuration requise pour le navigateur

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