Een identifier kiezen: UUID's, korte ID's en de soorten die informatie lekken
Gebruik een UUID v4 voor alles wat intern is, een v7 wanneer de identifier de primaire sleutel van een grote tabel is, en een korte code uit een beperkt alfabet wanneer iemand hem hardop moet voorlezen. Oplopende gehele getallen zijn de optie die je bij alles wat publiek is moet vermijden.
UUID-versies die ertoe doen
Een UUID is 128 bits. Vier daarvan dragen de versie en twee de variant, dus geen enkele versie heeft alle 128 te besteden.
| Versie | Wat het bevat | Gebruik hem wanneer |
|---|---|---|
| v1 | 60-bits tijdstempel, 14-bits klokvolgnummer, 48-bits node-ID (het MAC-adres) | Alleen bij oude data |
| v3 / v5 | Een MD5- of SHA-1-hash van een namespace en een naam | Deterministische ID's nodig zijn |
| v4 | 122 willekeurige bits, verder niets | Standaard, wanneer er niets af te leiden mag zijn |
| v7 | 48-bits tijdstempel in milliseconden, daarna 74 willekeurige bits | Sleutels waarbij de invoegvolgorde telt |
v1 is het waarschuwende geval. Hij bevat het tijdstip van aanmaak en, in de meeste implementaties, het MAC-adres van de machine die hem maakte, en zo hielpen zulke identifiers in 1999 bij het opsporen van de maker van het Melissa-virus. v7, in 2024 gestandaardiseerd in RFC 9562, publiceert het tijdstip van aanmaak met opzet: dat is zijn functie en zijn prijs.
De vraag over botsingen
Een UUID v4 heeft 122 willekeurige bits, dus er zijn er 2^122, ruwweg 5,3 x 10^36. Wat telt is de verjaardagsgrens, niet de omvang van de ruimte: de kans dat twee van n waarden gelijk zijn, is ongeveer n in het kwadraat gedeeld door 2^123. Een biljoen gegenereerde UUID's dragen ongeveer één kans op tien biljoen op een duplicaat, en voor een kans van vijftig procent zijn zo'n 2,7 x 10^18 waarden nodig, ongeveer 85 jaar bij een miljard per seconde.
"In de praktijk geen zorg" is nog steeds niet "onmogelijk", en het gat zit nooit in de rekensom. Duplicaten komen uit de generator: een vaste seed, een virtuele machine die met entropiepool en al gekloond is, een containerimage waarin opgeslagen PRNG-toestand meegaat. Uniciteit hoort bij de willekeurige bron, niet bij het formaat, dus houd de unique constraint op de kolom.
Volgorde, indexlocaliteit en lekkage
Willekeurige identifiers verspreiden zich. In een B-tree-index landt elke invoeging op een willekeurige leaf page, dus de working set is de hele index in plaats van de rechterrand ervan, cache hit rates dalen en pagina's splitsen. Een sleutel die in de tijd oploopt voegt op één plek toe, waardoor dezelfde paar pagina's warm blijven. Daarom bestaan v7 en ULID; ULID is 48 bits tijdstempel in milliseconden plus 80 willekeurige bits, geschreven als 26 sorteerbare tekens Crockford base32.
De prijs is voorspelbaarheid: een op tijd geordend ID publiceert wanneer het record is aangemaakt, dus een handvol ervan onthult aanmeldcijfers en rustige uren. Als dat uitmaakt, gebruik dan v4 en betaal de indexprijs.
Oplopende gehele getallen gaan verder. /invoices/1041 zegt dat je ongeveer duizend facturen hebt verstuurd, en twee ID's met een week ertussen geven je groeicijfer. Erger nog, een aanvaller kan 1040 opvragen. Als de server dat record teruggeeft omdat het bestaat en de aanvrager is ingelogd, zonder te controleren wie de eigenaar is, is dat een fout in de toegangscontrole, gecatalogiseerd als insecure direct object reference. Een onraadbaar ID is geen autorisatiecontrole, maar het voorkomt wel dat iemand de tabel afloopt.
Slugs zijn ook identifiers, en ze lekken niets over volume als ze goed gebouwd zijn: accenten weggehaald, alles in kleine letters, leestekens samengetrokken tot koppeltekens, zoals de Slug Generator doet. Houd een slug na publicatie stabiel; hem veranderen breekt inkomende links.
Korte ID's, alfabetten en lengte
Elk teken van een willekeurige code is log2 van de omvang van het alfabet waard: 5,95 bits voor base62, 5 voor base32, 4 voor hexadecimaal. Lengte en alfabet bepalen samen de botsingskans bij een gegeven volume.
| Lengte (base62) | Aantal waarden | Bits | Botsingskans binnen 1 miljard ID's |
|---|---|---|---|
| 8 | 2,2 x 10^14 | 43,7 | Zeker |
| 10 | 8,4 x 10^17 | 59,5 | Ongeveer 45% |
| 12 | 3,2 x 10^21 | 71,5 | Ongeveer 1 op 6.500 |
| 16 | 4,8 x 10^28 | 95,3 | Ongeveer 1 op 100 miljard |
Dat gaat ervan uit dat elk teken onafhankelijk willekeurig is; een voorvoegsel als inv_ voegt nul bits toe.
Voor alles wat een mens leest is base62 het verkeerde alfabet. 0 en O, 1 en l en I zijn overschrijffouten die staan te wachten, en een hoofdlettergevoelige code overleeft geen telefoongesprek. Crockford base32 laat I, L, O en U weg en accepteert bij invoer beide schrijfwijzen, het extra teken waard.
Waar de willekeurige bits vandaan komen
Math.random() is geen cryptografische bron. V8 implementeert het met xorshift128+, waarvan de interne toestand uit een korte reeks uitvoerwaarden te herleiden is, waarna elke volgende waarde voorspelbaar is. Prima voor een demo-shuffle of een tijdelijke vulling. Verkeerd voor een sessie-identifier, een API-sleutel, een herstellink of een token.
// Browsers, Node 19+, Deno and Bun
const id = crypto.randomUUID();
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
Elders: crypto.randomBytes in Node, secrets.token_urlsafe in Python, crypto/rand in Go, SecureRandom in Java, random_bytes in PHP. De UUID Generator produceert cryptografisch willekeurige v4-waarden en de Password Generator gebruikt dezelfde browser-API; Random Numbers is bedoeld voor steekproeven, niet voor geheimen.
Eén valkuil als je het zelf bouwt: een willekeurige byte met % 62 op een alfabet van 62 tekens afbeelden geeft een scheve verdeling richting de eerste acht tekens; gebruik rejection sampling.
Vul- en voorbeelddata
Lorem ipsum is verhaspelde Cicero, en het houdt stand omdat het onleesbaar is: tekst die op taal lijkt zonder taal te zijn, laat een opmaak beoordelen op vorm en regellengte in plaats van op de zinnen, en dat is wat de Lorem Ipsum generator produceert. Vervang het door realistische tekst voordat iets wordt goedgekeurd, want potjeslatijn verbergt de overloop die een echte kop veroorzaakt.
Verzonnen data heeft één regel nodig: het mag nooit voor het echte werk aangezien kunnen worden.
- Kaartnummers komen uit het gepubliceerde testbereik van een betaalprovider, of ze zakken botweg voor de Luhn-controle. Een willekeurig getal van 16 cijfers dat door Luhn komt, is van iemand.
- Nationale identificatienummers gebruiken gereserveerde reeksen. Amerikaanse Social Security-nummers die met 000, 666 of 900 tot en met 999 beginnen worden nooit uitgegeven.
- Gebruik
example.comen de verwante domeinen, gereserveerd door RFC 2606, en het documentatieblok 192.0.2.0/24.
Testdata belandt vaker in productie dan verwacht: een seed-script dat op de verkeerde database is gericht, een tijdelijke vulling die in een e-mailsjabloon blijft staan en naar de hele lijst gaat. Nepgegevens horen er overduidelijk nep uit te zien, zodat iemand ze opmerkt voordat de klantenservice ernaar handelt.
QR-codes in het kort
Een QR-code is een raster van modules, versies 1 tot en met 40, van 21x21 tot 177x177. De lengte van de inhoud bepaalt de versie, en bij een vaste afdrukmaat betekent een hogere versie kleinere modules, wat betere druk en een scan van dichterbij vereist. Kort de URL in voordat je hem aan de QR Code Generator geeft, en let erop dat de alfanumerieke modus alleen hoofdletters kent, dus HTTPS://EXAMPLE.COM codeert kleiner dan de variant in kleine letters.
Foutcorrectie heeft vier niveaus, die ruwweg 7% (L), 15% (M), 25% (Q) en 30% (H) van een beschadigde code herstellen. Redundantie kost capaciteit, dus een hoger niveau duwt dezelfde inhoud naar een dichtere versie. M is de gebruikelijke standaard; kies Q of H voor kleine afdrukken, gebogen oppervlakken, of een logo over het midden, wat werkt omdat de redundantie het opvangt.
Twee fysieke eisen bepalen of een code scant. De quiet zone is een vrije marge van vier modules aan elke kant, die de decoder gebruikt om de rand te vinden, dus een code die tegen een kader of foto aan staat mislukt vaak. Het contrast moet echt donker op echt licht zijn: lichtblauw op wit is een veelvoorkomende misser, en veel decoders weigeren geïnverteerde codes.