Collisioni durante la generazione di UUID in JavaScript?


94

Ciò si riferisce a questa domanda . Sto usando il codice seguente da questa risposta per generare l'UUID in JavaScript:

'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
});

Questa soluzione sembrava funzionare bene, ma sto ricevendo collisioni. Ecco cosa ho:

  • Un'app web in esecuzione in Google Chrome.
  • 16 utenti.
  • circa 4000 UUID sono stati generati negli ultimi 2 mesi da questi utenti.
  • Ho avuto circa 20 collisioni - ad esempio, il nuovo UUID generato oggi era lo stesso di circa 2 mesi fa (utente diverso).

Qual è la causa del problema e come posso evitarlo?


2
Combina un buon numero casuale con l'ora corrente (in millisecondi). Le probabilità che il numero casuale entri in collisione esattamente nello stesso momento sono davvero, davvero, davvero basse.
jfriend00

7
@ jfriend00 se hai bisogno di farlo allora non è un "buon numero casuale", nemmeno un numero pseudo-casuale decente.
Attila O.

2
a cosa (r&0x3|0x8)significa / valutazione la porzione?
Kristian

Che ne dici di aggiungere un Date.now (). ToString () ad esso?
Vitim.us

4
C'è un grosso problema nella tua architettura, non correlato agli UUID: il client potrebbe generare intenzionalmente ID in conflitto. Genera ID solo da un sistema di cui ti fidi. Come soluzione alternativa, però, anteponi gli ID generati dal client con user_id, in modo che il client avversario / difettoso possa entrare in collisione solo con se stesso (e gestirlo sul lato server).
Dzmitry Lazerka

Risposte:


35

La mia ipotesi migliore è che Math.random()il tuo sistema sia guasto per qualche motivo (bizzarro come sembra). Questo è il primo rapporto che ho visto di qualcuno che subisce collisioni.

node-uuidha un test harness che puoi usare per testare la distribuzione di cifre esadecimali in quel codice. Se sembra a posto, allora non lo è Math.random(), quindi prova a sostituire l'implementazione dell'UUID che stai utilizzando nel uuid()metodo lì e vedi se ottieni ancora buoni risultati.

[Aggiornamento: ho appena visto il rapporto di Veselin sul bug Math.random()all'avvio. Poiché il problema è solo all'avvio, node-uuidè improbabile che il test sia utile. Commenterò in modo più dettagliato il link devoluk.com.]


1
Grazie, ora vado con uuid.js, poiché utilizza la crittografia avanzata del browser, se disponibile. Vedrà se ci sono collisioni.
Muxa

puoi fornire un collegamento al codice uuid.js a cui ti riferisci? (mi dispiace, non sono sicuro di quale
libreria

10
Finora non ci sono state collisioni :)
Muxa

Ad ogni modo, se è Chrome e solo all'avvio, la tua app potrebbe generare e scartare una riga di, diciamo, dieci guide utilizzando la funzione sopra :)
Vinko Vrsalovic

Il problema è con l'entropia limitata che ottieni da Math.random (). Per alcuni browser l'entropia è di soli 41 bit in totale. Chiamare Math.random () più volte non aumenterà l'entropia. Se vuoi davvero UUID v4 univoci, devi utilizzare un RNG crittograficamente potente che produca almeno 122 bit di entropia per UUID generato.
mlehmk

36

Effettivamente ci sono collisioni ma solo sotto Google Chrome. Dai un'occhiata alla mia esperienza sull'argomento qui

http://devoluk.com/google-chrome-math-random-issue.html

(Link interrotto a partire dal 2019. Link all'archivio: https://web.archive.org/web/20190121220947/http://devoluk.com/google-chrome-math-random-issue.html .)

Sembra che le collisioni avvengano solo durante le prime chiamate di Math.random. Perché se esegui semplicemente il metodo createGUID / testGUIDs sopra (che ovviamente è stata la prima cosa che ho provato) funziona senza collisioni di sorta.

Quindi per fare un test completo è necessario riavviare Google Chrome, generare 32 byte, riavviare Chrome, generare, riavviare, generare ...


2
È piuttosto preoccupante: qualcuno ha sollevato una segnalazione di bug?
UpTheCreek

1
Soprattutto come il collegamento a migliori generatori di numeri casuali in javascript: baagoe.com/en/RandomMusings/javascript
Leopd

purtroppo, detto collegamento è ora interrotto :(
Gus


7
Qualcuno può confermare se questo bug è stato risolto?
Xdrone

20

Solo così che altre persone possano essere consapevoli di questo: mi sono imbattuto in un numero sorprendentemente elevato di collisioni apparenti utilizzando la tecnica di generazione dell'UUID menzionata qui. Queste collisioni sono continuate anche dopo che sono passato a seedrandom per il mio generatore di numeri casuali. Questo mi ha fatto strappare i capelli, come puoi immaginare.

Alla fine ho capito che il problema era (quasi?) Esclusivamente associato ai bot del web crawler di Google. Non appena ho iniziato a ignorare le richieste con "googlebot" nel campo user-agent, le collisioni sono scomparse. Immagino che debbano memorizzare nella cache i risultati degli script JS in un modo semi-intelligente, con il risultato finale che il loro browser spider non può essere considerato come comportarsi come fanno i normali browser.

Solo un FYI.


2
Ho riscontrato lo stesso problema con il nostro sistema di metriche. Ho visto migliaia di collisioni UUID utilizzando il modulo "node-uuid" per generare ID di sessione nel browser. È venuto fuori che è sempre stato Googlebot. Grazie!
domkck

4

Volevo postare questo come commento alla tua domanda, ma a quanto pare StackOverflow non me lo permette.

Ho appena eseguito un test rudimentale di 100.000 iterazioni in Chrome utilizzando l'algoritmo UUID che hai pubblicato e non ho riscontrato collisioni. Ecco uno snippet di codice:

var createGUID = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
        return v.toString(16);
    });
}

var testGUIDs = function(upperlimit) {
    alert('Doing collision test on ' + upperlimit + ' GUID creations.');
    var i=0, guids=[];
    while (i++<upperlimit) {
        var guid=createGUID();
        if (guids.indexOf(guid)!=-1) {
            alert('Collision with ' + guid + ' after ' + i + ' iterations');
        }
        guids.push(guid);
    }
    alert(guids.length + ' iterations completed.');
}

testGUIDs(100000);

Sei sicuro che non ci sia qualcos'altro qui?


4
Sì, ho eseguito anche alcuni test locali e non ho riscontrato collisioni. Le collisioni si verificano tra gli UUID che vengono generati su macchine di utenti diversi. Potrei aver bisogno di generare alcuni dati su macchine diverse e controllare le collisioni.
Muxa

2
Inoltre, ho notato che le collisioni sono tra gli UUID generati a 3-4 settimane di distanza.
Muxa

Molto strano. Su quale piattaforma stai correndo?
user533676

1
Sembra improbabile che ci sia un difetto così basilare in Math.random () di V8, ma Chromium 11 ha aggiunto il supporto per una forte generazione di numeri casuali usando l'API window.crypto.getRandomValues ​​se vuoi provarlo invece. Vedi blog.chromium.org/2011/06/… .
user533676

Funziona con una combinazione di Windows 7 e Windows XP.
Muxa

3

La risposta che originariamente aveva pubblicato questa soluzione UUID è stata aggiornata il 28/06/2017:

Un buon articolo degli sviluppatori di Chrome che discute dello stato della qualità PRNG Math.random in Chrome, Firefox e Safari. tl; dr - Alla fine del 2015 è "abbastanza buono", ma non di qualità crittografica. Per risolvere questo problema, ecco una versione aggiornata della soluzione di cui sopra che utilizza ES6, l' cryptoAPI e un po 'di JS wizardy di cui non posso prendermi il merito :

function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  )
}

console.log(uuidv4());


0

Le risposte qui riguardano "qual è la causa del problema?" (Problema seed di Chrome Math.random) ma non "come posso evitarlo?".

Se stai ancora cercando come evitare questo problema, ho scritto questa risposta qualche tempo fa come una versione modificata della funzione di Broofa per aggirare questo problema esatto. Funziona compensando i primi 13 numeri esadecimali con una porzione esadecimale del timestamp, il che significa che anche se Math.random è sullo stesso seme, genererà comunque un UUID diverso a meno che non venga generato esattamente nello stesso millisecondo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.