243.583.606.221.817.150.598.111.409x mehr Entropie
Ich würde die Verwendung empfehlen crypto.randomBytes. Es ist nicht sha1
aber für ID-Zwecke ist es schneller und genauso “zufällig”.
var id = crypto.randomBytes(20).toString('hex');
//=> f26d60305dae929ef8640a75e70dd78ab809cfe9
Die resultierende Zeichenfolge ist doppelt so lang wie die von Ihnen generierten zufälligen Bytes. Jedes in Hex codierte Byte besteht aus 2 Zeichen. 20 Bytes sind 40 Hexadezimalzeichen.
Mit 20 Bytes haben wir 256^20
oder 1.461.501.637.330.902.918.203.684.832.716.283.019.655.932.542.976 eindeutige Ausgabewerte. Das ist identisch zu den möglichen 160-Bit (20-Byte)-Ausgängen von SHA1.
Wenn wir das wissen, ist es für uns nicht wirklich sinnvoll shasum
unsere Zufallsbytes. Es ist, als würde man zweimal würfeln, aber nur den zweiten Wurf akzeptieren; Egal was passiert, Sie haben 6 mögliche Ergebnisse bei jedem Wurf, also ist der erste Wurf ausreichend.
Warum ist das besser?
Um zu verstehen, warum dies besser ist, müssen wir zunächst verstehen, wie Hash-Funktionen funktionieren. Hashing-Funktionen (einschließlich SHA1) erzeugen immer dieselbe Ausgabe, wenn dieselbe Eingabe gegeben wird.
Angenommen, wir möchten IDs generieren, aber unsere zufällige Eingabe wird durch einen Münzwurf generiert. Wir haben "heads"
oder "tails"
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
Wenn "heads"
wieder auftaucht, wird der SHA1-Ausgang sein gleich wie es das erste Mal war
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
Ok, ein Münzwurf ist also kein großartiger Zufallsgenerator, da wir nur 2 mögliche Ausgänge haben.
Wenn wir einen standardmäßigen 6-seitigen Würfel verwenden, haben wir 6 mögliche Eingaben. Ratet mal, wie viele mögliche SHA1-Ausgänge? 6!
input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278
Es ist leicht, uns selbst etwas vorzumachen, indem wir nur an den Output unserer Funktion denken sieht aus sehr zufällig, dass es ist sehr zufällig.
Wir sind uns beide einig, dass ein Münzwurf oder ein 6-seitiger Würfel einen schlechten Zufalls-ID-Generator abgeben würden, da unsere möglichen SHA1-Ergebnisse (der Wert, den wir für die ID verwenden) sehr wenige sind. Aber was ist, wenn wir etwas verwenden, das viel mehr Ausgänge hat? Wie ein Zeitstempel mit Millisekunden? Oder JavaScripts Math.random
? Oder sogar ein Kombination von den beiden?!
Lassen Sie uns berechnen, wie viele eindeutige IDs wir erhalten würden …
Die Eindeutigkeit eines Zeitstempels mit Millisekunden
Beim Benutzen (new Date()).valueOf().toString()
erhalten Sie eine 13-stellige Zahl (z. B. 1375369309741
). Da dies jedoch eine sequentiell aktualisierte Zahl ist (einmal pro Millisekunde), sind die Ausgaben fast immer gleich. Lass uns einen Blick darauf werfen
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
Um fair zu sein, zu Vergleichszwecken, in einer bestimmten Minute (eine großzügige Operationsausführungszeit), die Sie haben werden 60*1000
oder 60000
Unikate.
Die Einzigartigkeit von Math.random
Nun, bei der Verwendung Math.random
, aufgrund der Art und Weise, wie JavaScript 64-Bit-Gleitkommazahlen darstellt, erhalten Sie eine Zahl mit einer Länge zwischen 13 und 24 Zeichen. Ein längeres Ergebnis bedeutet mehr Ziffern, was mehr Entropie bedeutet. Zuerst müssen wir herausfinden, welche die wahrscheinlichste Länge ist.
Das folgende Skript bestimmt, welche Länge am wahrscheinlichsten ist. Wir tun dies, indem wir 1 Million Zufallszahlen generieren und einen darauf basierenden Zähler erhöhen .length
jeder Zahl.
// 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 });
Indem wir jeden Zähler durch 1 Million dividieren, erhalten wir die Wahrscheinlichkeit der Länge der zurückgegebenen Zahl 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
Also, auch wenn es nicht ganz stimmt, seien wir großzügig und sagen Sie, Sie erhalten eine 19 Zeichen lange Zufallsausgabe; 0.1234567890123456789
. Die ersten Zeichen werden immer sein 0
und .
, also bekommen wir wirklich nur 17 zufällige Zeichen. Dies lässt uns mit 10^17
+1
(für evtl 0
; siehe Anmerkungen unten) oder 100.000.000.000.000.001 Unikate.
Wie viele zufällige Eingaben können wir also generieren?
Ok, wir haben die Anzahl der Ergebnisse für einen Millisekunden-Zeitstempel berechnet und Math.random
100,000,000,000,000,001 (Math.random)
* 60,000 (timestamp)
-----------------------------
6,000,000,000,000,000,060,000
Das ist ein einzelner Würfel mit 6.000.000.000.000.000.060.000 Seiten. Oder, um diese Zahl menschlich verdaulicher zu machen, das ist grob die gleiche Nummer wie
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
Klingt ziemlich gut, oder? Nun, lass es uns herausfinden …
SHA1 erzeugt einen 20-Byte-Wert mit möglichen 256^20 Ergebnissen. Wir nutzen SHA1 also wirklich nicht in vollem Umfang. Nun, wie viel verwenden wir?
node> 6000000000000000060000 / Math.pow(256,20) * 100
Ein Millisekunden-Zeitstempel und Math.random nutzt nur 4.11e-27 Prozent des 160-Bit-Potenzials von SHA1!
generator sha1 potential used
-----------------------------------------------------------------------------
crypto.randomBytes(20) 100%
Date() + Math.random() 0.00000000000000000000000000411%
6-sided die 0.000000000000000000000000000000000000000000000411%
A coin 0.000000000000000000000000000000000000000000000137%
Heilige Katzen, Mann! Schau dir all diese Nullen an. Also wie viel besser ist crypto.randomBytes(20)
? 243.583.606.221.817.150.598.111.409 mal besser.
Notizen über die +1
und Häufigkeit von Nullen
Wenn Sie sich über die wundern +1
es ist möglich für Math.random
zurückgeben a 0
was bedeutet, dass es 1 weiteres mögliches eindeutiges Ergebnis gibt, das wir berücksichtigen müssen.
Basierend auf der Diskussion, die unten stattfand, war ich neugierig auf die Frequenz a 0
würde kommen. Hier ist ein kleines Skript, random_zero.js
machte ich, um einige Daten zu bekommen
#!/usr/bin/env node
var count = 0;
while (Math.random() !== 0) count++;
console.log(count);
Dann habe ich es in 4 Threads ausgeführt (ich habe einen 4-Kern-Prozessor) und die Ausgabe an eine Datei angehängt
$ yes | xargs -n 1 -P 4 node random_zero.js >> zeroes.txt
Es stellt sich also heraus, dass a 0
ist nicht so schwer zu bekommen. Nach 100 Werte aufgezeichnet wurden, war der Durchschnitt
1 in 3.164.854.823 Zufall ist eine 0
Kühl! Weitere Untersuchungen wären erforderlich, um zu wissen, ob diese Zahl einer gleichmäßigen Verteilung von v8 entspricht Math.random
Implementierung
Wenn Sie eindeutige Kennungen erhalten möchten, sollten Sie UUID (Universally Unique Identifier) / GUID (Globally Unique Identifier) verwenden.
Ein Hash soll deterministisch und einzigartig sein und eine feste Länge für Eingaben jeder Größe haben. Unabhängig davon, wie oft Sie die Hash-Funktion ausführen, ist die Ausgabe dieselbe, wenn Sie dieselbe Eingabe verwenden.
UUIDs sind einzigartig und werden zufällig generiert! Es gibt ein Paket namens ‚uuid‘, das Sie über npm installieren können
npm installiert uuid
& Importieren Sie in Ihrem Code das Modul by
const { v4:uuidv4} = require(‘uuid’);
// Rufen Sie die Methode uuidv4 oder wie auch immer Sie sie beim Importieren nennen auf und protokollieren Sie sie oder speichern Sie sie oder weisen Sie sie zu. Die Methode gibt eine UUID in Form eines Strings zurück.
console.log(uuidv4()); // Beispielausgabe: ‘59594fc8-6a35-4f50-a966-4d735d8402ea’
Hier ist der npm-Link (falls Sie ihn brauchen):
https://www.npmjs.com/package/uuid
Verwenden Sie nicht sha1. Es gilt nicht mehr als sicher (kollisionsresistent). Deshalb ist Naomiks Antwort besser.
– Niels Abildgaard
17. Februar 2015 um 9:07 Uhr
@Niels Abildgaard Es ist trivial, nach der Generierung nach Duplikaten zu suchen, und das Kollisionsrisiko ist nicht unbedingt relevant. Sie müssen unabhängig davon, was Sie verwenden, nach Duplikaten suchen, wenn die Größe Ihrer Datenbank zunimmt. “Sicher” hat nichts mit Kardinalität zu tun.
– mckenzm
4. November 2021 um 20:52 Uhr
@mckenzm Nichts davon muss unbedingt wahr sein: Sie haben keine Ahnung, wofür die IDs verwendet werden, wie Elemente mit IDs erstellt werden, welche Annahmen für sie in der konkreten Anwendung gelten (und daher, ob die Kollisionsfestigkeit von Bedeutung ist). Und auf Dubletten muss man sowieso nicht prüfen, das ist der ganze Sinn von kollisionsresistenten IDs und warum sie gerade in großen und dezentralen Anwendungen zum Einsatz kommen.
– Niels Abildgaard
5. November 2021 um 10:06 Uhr
@ Niels Abildgaard, OP will nur einen ausgewogenen synthetischen Schlüssel. Aus dem Nichts. Normalerweise würden wir einen Zeitstempel umkehren oder die aktuelle Zeilenanzahl hashen. Es muss nur gut verteilt und eindeutig in der Tabelle sein, was der Schlüssel erzwingt. Dann ist ein weiterer Versuch erforderlich. Kollisionsfestigkeit ist gut, aber nie absolut.
– mckenzm
6. November 2021 um 0:29 Uhr