JavaScript: Wie erzeugt man eine UUID?

Dafür gibt es inzwischen eine eingebaute Funktion, die Antwort ist also eine Zeile. Lesenswert ist der Teil danach: warum der immer noch kursierende Math.random()-Einzeiler etwas erzeugt, das identisch aussieht und es nicht ist.

Die eingebaute Variante

crypto.randomUUID();
crypto.randomUUID() : ffa090d3-7fbd-4bf3-8d97-890afad4d395

crypto.randomUUID() ist im Browser und in Node Standard, kryptografisch zufällig und braucht keine Abhängigkeit.

Was die Ziffern bedeuten

 fb4dacbb-4555-4bcd-9d4c-68f06907de67
               ^    ^
  the 13th hex digit is always 4 (the version)
  the 17th is one of 8, 9, a, b (the variant)
length: 36  groups: 8-4-4-4-12

Zwei der 32 Hex-Ziffern sind nicht zufällig. Die 4 sagt, dass es Version 4 ist — zufallsbasiert, im Gegensatz zur Zeitstempel- und MAC-basierten Version 1 — und die Variantenziffer sagt, welchem Layout der Rest folgt. Übrig bleiben 122 Zufallsbits, nicht 128.

Eindeutig genug?

200000 generated, 200000 distinct -> 0 collisions

Dieser Lauf beweist für sich genommen nichts; 200.000 aus 2¹²² wären nie kollidiert. Die Zahl, die die Frage beantwortet, ist die Geburtstagsschranke: Für eine Kollision mit 50 Prozent Wahrscheinlichkeit brauchst du rund 2,7 × 10¹⁸ UUIDs. So viele erzeugst du nicht.

Die Math.random()-Variante

looks fine : 36ff6e83-c8d6-4099-a130-1bcaaf63fd03

Richtige Länge, richtige Gruppen, richtige Versionsziffer. Nichts an der Ausgabe verrät, dass sie anders ist — genau deshalb landet der Einzeiler weiter an Stellen, an denen er nichts zu suchen hat.

Math.random() ist kein kryptografischer Generator und behauptet das auch nicht. V8 implementiert es mit xorshift128+, pro Kontext geseedet; mit genug Ausgabe lässt sich der Zustand rekonstruieren und damit auch vergangene und künftige Werte.

Also: in Ordnung für eine DOM-ID oder einen React-Key. Nicht für ein Session-Token, einen Passwort-Reset-Link oder einen API-Schlüssel.

Auch der Wertebereich ist wissenswert:

highest of 1,000,000 draws: 0.9999992620226952 ( always < 1 )

Math.random() liefert [0, 1) — nie 1. Jede Skalierung, die davon ausgeht, dass das obere Ende erreichbar ist, liegt um einen Fall daneben.

Selbst bauen

Wenn du die Bytes brauchst:

const b = crypto.getRandomValues(new Uint8Array(16));
b[6] = (b[6] & 0x0f) | 0x40;   // Version 4
b[8] = (b[8] & 0x3f) | 0x80;   // Variante
5d2ae888-b1e6-4ab4-b19c-92a5dfeb5511  version: 4  variant: b

Die beiden maskierten Bytes sind das, was daraus eine gültige v4 macht statt 16 zufälliger Bytes mit Bindestrichen darin.

Wenn eine UUID zu viel ist

16 random bytes as hex   : d5e610b00f536b932eea7d8e5aee3018
16 random bytes base64url: bG7V6rQ4pEnAjtb1AcLCPg

Dieselben 128 Bit, weniger Zeichen. Keine UUID, und nicht sortierbar — willst du IDs, die sich nach Erzeugungszeit sortieren, schau dir UUIDv7 an, statt etwas Eigenes zu erfinden.

Warum es manchmal undefined ist

browsers: needs a secure context (https or localhost).

crypto.randomUUID ist auf einen sicheren Kontext beschränkt. Über einfaches http:// ist es schlicht nicht vorhanden, und der Fehler, den du bekommst, ist ein TypeError über den Aufruf von undefined. Das ist fast immer der Grund, warum es “nicht funktioniert” — kein alter Browser, sondern eine über http ausgelieferte Seite.

Hinweis zu Netcup (Werbung)

Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).