Wie generiert man einen zufälligen SHA1-Hash zur Verwendung als ID in node.js?

Lesezeit: 11 Minuten

Benutzer-Avatar
ajsie

Ich verwende diese Zeile, um eine sha1-ID für node.js zu generieren:

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

Das Problem ist, dass es jedes Mal die gleiche ID zurückgibt.

Ist es möglich, jedes Mal eine zufällige ID generieren zu lassen, damit ich sie als Datenbankdokument-ID verwenden kann?

  • 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

Benutzer-Avatar
Mulan

243.583.606.221.817.150.598.111.409x mehr Entropie

Ich würde die Verwendung empfehlen crypto.randomBytes. Es ist nicht sha1aber 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 +1es 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.jsmachte 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

  • crypto.randomBytes ist auf jeden Fall der richtige Weg^^

    – Mulan

    6. Juni 2017 um 4:43 Uhr

  • Würde nicht A millisecond timestamp and Math.random uses only 4.11e-27 percent of SHA1's 160-bit potential! eigentlich sein 4.11e-25 percent? 6e21 / 256**20 * 100 ist 4.1053665947016126e-25. Das würde das auch bedeuten 0.00000000000000000000000000411% sollte eigentlich sein 0.000000000000000000000000411%etc.

    – Patrick Roberts

    28. September 2018 um 15:19 Uhr


Benutzer-Avatar
Gabi Purcaru

Schau mal hier: Wie verwende ich node.js Crypto, um einen HMAC-SHA1-Hash zu erstellen? Ich würde einen Hash des aktuellen Zeitstempels + eine Zufallszahl erstellen, um die Eindeutigkeit des Hashs sicherzustellen:

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

  • Einen viel besseren Ansatz finden Sie in der Antwort von @naomik unten.

    – Gabi Purcaru

    17. August 2013 um 14:41 Uhr

  • Das war auch eine tolle Antwort Gabi, und nur ein kleines bisschen schneller, ca. 15%. Tolle Arbeit beide! Ich sehe eigentlich gerne ein Date() im Salz, es gibt einem Entwickler die Gewissheit, dass dies in allen außer den verrücktesten parallelen Rechensituationen ein einzigartiger Wert sein wird. Ich weiß, dass seine dummen und randomBytes (20) einzigartig sein werden, aber es ist nur ein Vertrauen, das wir haben können, weil wir möglicherweise nicht mit den Interna der zufälligen Generierung einer anderen Bibliothek vertraut sind.

    – Dimitri R117

    5. September 2017 um 15:13 Uhr

  • @Dmitri R117 Was ist der Vorteil des Salzens eines Primärschlüssels? Eindeutige Werte müssen nicht garantiert werden, da Duplikate beim Einfügen erkannt werden können. Einige Shops weisen Blöcke im Voraus zu. Wichtiger ist, dass die Verteilung für einen flacheren Index oder gleich große Partitionen homogen ist.

    – mckenzm

    4. November 2021 um 20:57 Uhr

Benutzer-Avatar
Mulan

Machen Sie es auch im Browser!

BEARBEITEN: Das passte nicht wirklich in den Fluss meiner vorherigen Antwort. Ich belasse es hier als zweite Antwort für Leute, die dies möglicherweise im Browser tun möchten.

Sie können dies clientseitig in modernen Browsern tun, wenn Sie möchten

// 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"

Browseranforderungen

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

  • Number.toString(radix) garantiert nicht immer einen zweistelligen Wert (z. B.: (5).toString(16) = “5”, nicht “05”). Dies spielt keine Rolle, es sei denn, Sie sind davon abhängig, dass Ihre endgültige Ausgabe genau ist len Zeichen lang. In diesem Fall können Sie verwenden return ('0'+n.toString(16)).slice(-2); innerhalb Ihrer Kartenfunktion.

    – Der stämmige Mann

    20. Juli 2015 um 20:51 Uhr


  • Toller Code, danke. Ich wollte nur hinzufügen: Wenn Sie es für den Wert von verwenden werden id vergewissern Sie sich, dass die ID mit einem Buchstaben beginnt: [A-Za-z].

    – GijsjanB

    10. Januar 2019 um 11:43 Uhr

  • Tolle Antwort (und Kommentare) – sehr dankbar, dass Sie auch die Browseranforderungen in die Antwort aufgenommen haben!

    – Kevlarr

    15. März 2020 um 15:58 Uhr

  • Die Browseranforderungen sind falsch. Array.from() ist nicht in IE11 unterstützt.

    – OT

    30. Juni 2020 um 19:13 Uhr

  • Es wurde zum Zeitpunkt dieser Antwort aus einem Wiki entnommen. Sie können diese Antwort bearbeiten, wenn Sie möchten, aber wen interessiert der IE wirklich? Wenn Sie versuchen, es zu unterstützen, müssen Sie sowieso die Hälfte von JavaScript polyfillen …

    – Mulan

    2. Juli 2020 um 5:00 Uhr

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 crypto ist ein guter Ansatz, da es sich um ein natives und stabiles Modul handelt, aber es gibt Fälle, in denen Sie es verwenden können bcrypt wenn Sie einen wirklich starken und sicheren Hash erstellen möchten. Ich verwende es für Passwörter, es hat viele Techniken zum Hashen, Erstellen von Salz und Vergleichen von Passwörtern.

Technik 1 (Erzeuge ein Salt und einen Hash bei separaten Funktionsaufrufen)

const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(myPlaintextPassword, salt);

Technik 2 (automatisches Generieren von Salt und Hash):

const hash = bcrypt.hashSync(myPlaintextPassword, saltRounds);

Weitere Beispiele finden Sie hier: https://www.npmjs.com/package/bcrypt

1270800cookie-checkWie generiert man einen zufälligen SHA1-Hash zur Verwendung als ID in node.js?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy