Scegliere un identificatore: UUID, ID brevi e quelli che lasciano trapelare informazioni

Quale versione di UUID usare, perché le collisioni della v4 non sono una preoccupazione pratica, che cosa rivelano gli interi sequenziali, quali compromessi impongono gli alfabeti degli ID brevi e da dove devono arrivare i bit casuali.

Usa un UUID v4 per qualsiasi cosa interna, una v7 quando l'identificatore è la chiave primaria di una tabella grande, e un codice breve tratto da un alfabeto ristretto quando una persona deve leggerlo ad alta voce. Gli interi sequenziali sono l'opzione da evitare su tutto ciò che è pubblico.

Le versioni di UUID che contano

Un UUID è fatto di 128 bit. Quattro portano la versione e due la variante, quindi nessuna versione ha tutti e 128 da spendere.

VersioneChe cosa contieneQuando usarla
v1timestamp da 60 bit, sequenza di clock da 14 bit, ID del nodo da 48 bit (l'indirizzo MAC)Solo per dati preesistenti
v3 / v5Un hash MD5 o SHA-1 di uno spazio dei nomi e di un nomeQuando servono ID deterministici
v4122 bit casuali, nient'altroScelta predefinita, quando non si deve poter dedurre nulla
v7timestamp in millisecondi da 48 bit, poi 74 bit casualiChiavi in cui conta l'ordine di inserimento

La v1 è quella che serve da monito. Incorpora l'istante di creazione e, nella maggior parte delle implementazioni, l'indirizzo MAC della macchina che l'ha prodotta, ed è così che identificatori di questo tipo hanno aiutato a risalire all'autore del virus Melissa nel 1999. La v7, standardizzata nella RFC 9562 nel 2024, pubblica l'istante di creazione di proposito: è insieme la sua caratteristica e il suo costo.

La questione delle collisioni

Un UUID v4 ha 122 bit casuali, quindi ne esistono 2^122, all'incirca 5,3 x 10^36. Quello che conta è il limite del paradosso del compleanno, non la dimensione dello spazio: la probabilità che due valori qualsiasi fra n coincidano è circa n al quadrato diviso 2^123. Mille miliardi di UUID generati portano con sé una probabilità di duplicato di circa uno su diecimila miliardi, e per arrivare alla probabilità di un lancio di moneta servono circa 2,7 x 10^18 valori, ossia un'ottantina di anni al ritmo di un miliardo al secondo.

«Non è una preoccupazione pratica» non equivale comunque a «impossibile», e lo scarto non sta mai nell'aritmetica. I duplicati arrivano dal generatore: un seed fisso, una macchina virtuale clonata insieme al suo pool di entropia, un'immagine di container che si porta dietro uno stato del PRNG già salvato. L'unicità appartiene alla sorgente casuale, non al formato, quindi tieni il vincolo di unicità sulla colonna.

Ordinamento, località dell'indice e fughe di informazione

Gli identificatori casuali si sparpagliano. In un indice B-tree ogni inserimento finisce in una pagina foglia casuale, quindi il working set è l'intero indice invece del suo bordo destro, i tassi di hit della cache calano e le pagine si spezzano. Una chiave che cresce nel tempo si accoda in un solo punto, tenendo calde sempre le stesse poche pagine. È per questo che esistono la v7 e ULID; ULID è fatto di 48 bit di timestamp in millisecondi più 80 bit casuali, scritti come 26 caratteri ordinabili in base32 di Crockford.

Il costo è la prevedibilità: un ID ordinato nel tempo pubblica quando è stato creato il suo record, quindi ne bastano pochi per rivelare i ritmi di iscrizione e le ore di calma. Se la cosa conta, usa la v4 e paga il costo sull'indice.

Gli interi sequenziali vanno oltre. /invoices/1041 dice che hai emesso circa mille fatture, e due ID a una settimana di distanza danno il tuo tasso di crescita. Peggio ancora, un attaccante può chiedere il 1040. Se il server restituisce quel record perché esiste e chi chiama ha effettuato l'accesso, senza controllare a chi appartiene, si tratta di un difetto di controllo degli accessi, catalogato come insecure direct object reference. Un ID non indovinabile non è un controllo di autorizzazione, ma impedisce comunque a chiunque di scorrere l'intera tabella.

Anche gli slug sono identificatori, e se costruiti bene non lasciano trapelare nulla sui volumi: accenti appiattiti, tutto in minuscolo, punteggiatura ridotta a trattini, come fa il Generatore di slug. Tieni stabile uno slug dopo la pubblicazione; cambiarlo rompe i link in entrata.

ID brevi, alfabeti e lunghezza

Ogni carattere di un codice casuale vale il logaritmo in base 2 della dimensione dell'alfabeto: 5,95 bit per base62, 5 per base32, 4 per l'esadecimale. Lunghezza e alfabeto fissano la probabilità di collisione per un dato volume.

Lunghezza (base62)Valori distintiBitProbabilità di collisione entro 1 miliardo di ID
82,2 x 10^1443,7Certa
108,4 x 10^1759,5Circa il 45%
123,2 x 10^2171,5Circa 1 su 6.500
164,8 x 10^2895,3Circa 1 su 100 miliardi

Questo presuppone che ogni carattere sia casuale in modo indipendente; un prefisso come inv_ aggiunge zero bit.

Per qualsiasi cosa che debba leggere una persona, base62 è l'alfabeto sbagliato. 0 e O, 1 e l e I sono errori di trascrizione in attesa di accadere, e un codice che distingue maiuscole e minuscole non sopravvive a una telefonata. La base32 di Crockford elimina I, L, O e U e in ingresso accetta entrambe le forme, il che vale il carattere in più.

Da dove arrivano i bit casuali

Math.random() non è una sorgente crittografica. V8 la implementa con xorshift128+, il cui stato interno è ricostruibile da una breve serie di risultati, dopo di che ogni valore futuro è prevedibile. Va bene per mescolare qualcosa in una demo o per un segnaposto. È sbagliata per un identificatore di sessione, una chiave API, un link di reimpostazione o un token.

// Browsers, Node 19+, Deno and Bun
const id = crypto.randomUUID();

const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);

Altrove: crypto.randomBytes in Node, secrets.token_urlsafe in Python, crypto/rand in Go, SecureRandom in Java, random_bytes in PHP. Il Generatore di UUID produce valori v4 crittograficamente casuali e il Generatore di password usa la stessa API del browser; Numeri casuali serve per il campionamento, non per i segreti.

Una trappola quando ci si costruisce la propria soluzione: mappare un byte casuale su un alfabeto di 62 caratteri con % 62 sbilancia verso i primi otto caratteri; usa il campionamento con rigetto.

Dati segnaposto e dati di esempio

Il lorem ipsum è Cicerone storpiato, e sopravvive proprio perché è illeggibile: un testo che sembra lingua senza esserlo permette di giudicare un layout sulla forma e sulla lunghezza delle righe invece che sulle sue frasi, ed è quello che produce il generatore Lorem Ipsum. Sostituiscilo con testi realistici prima dell'approvazione finale, perché il latino segnaposto nasconde il traboccamento che provocherà un titolo vero.

I dati inventati hanno bisogno di una regola sola: non devono mai poter essere scambiati per quelli veri.

  • I numeri di carta si prendono dall'intervallo di test pubblicato da un fornitore di pagamenti, oppure devono fallire nettamente il controllo di Luhn. Un numero casuale di 16 cifre che supera Luhn appartiene a qualcuno.
  • Gli identificatori nazionali usano intervalli riservati. I numeri di Social Security statunitensi che iniziano con 000, 666 o da 900 a 999 non vengono mai emessi.
  • Usa example.com e i domini affini, riservati dalla RFC 2606, e il blocco per la documentazione 192.0.2.0/24.

I dati di test arrivano in produzione più spesso di quanto ci si aspetti: uno script di seed puntato sul database sbagliato, un segnaposto rimasto in un modello di email spedito a tutta la lista. I record finti devono sembrare palesemente finti, così che una persona se ne accorga prima che l'assistenza ci lavori sopra.

I codici QR in breve

Un codice QR è una griglia di moduli, dalla versione 1 alla 40, da 21x21 fino a 177x177. La lunghezza del payload determina la versione, e a parità di dimensione di stampa una versione più alta significa moduli più piccoli, che richiedono una stampa migliore e una scansione più ravvicinata. Accorcia l'URL prima di darlo in pasto al Generatore di codici QR, e tieni presente che la modalità alfanumerica accetta solo le maiuscole, quindi HTTPS://EXAMPLE.COM si codifica in meno spazio della forma minuscola.

La correzione d'errore ha quattro livelli, che recuperano circa il 7% (L), il 15% (M), il 25% (Q) e il 30% (H) di un codice danneggiato. La ridondanza costa capacità, quindi un livello più alto spinge lo stesso payload in una versione più densa. M è il valore predefinito abituale; scegli Q o H per stampe piccole, superfici curve o un logo sovrapposto al centro, che funziona proprio perché la ridondanza lo assorbe.

Due requisiti fisici decidono se un codice si legge. La quiet zone è un margine libero di quattro moduli su ogni lato, che il decodificatore usa per trovare il bordo, quindi un codice appiccicato a una cornice o a una fotografia spesso fallisce. Il contrasto deve essere davvero scuro su davvero chiaro: azzurro pallido su bianco è un errore frequente, e molti decodificatori rifiutano i codici invertiti.