¿Colisiones al generar UUID en JavaScript?

94

Esto se relaciona con esta pregunta . Estoy usando el código a continuación de esta respuesta para generar UUID en 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);
});

Esta solución parece estar funcionando bien, pero tengo colisiones. Esto es lo que tengo:

  • Una aplicación web que se ejecuta en Google Chrome.
  • 16 usuarios.
  • Estos usuarios han generado unos 4000 UUID en los últimos 2 meses.
  • Tuve alrededor de 20 colisiones, por ejemplo, el nuevo UUID generado hoy era el mismo que hace aproximadamente 2 meses (usuario diferente).

¿Qué está causando este problema y cómo puedo evitarlo?

Muxa
fuente
2
Combine un buen número aleatorio con la hora actual (en milisegundos). Las probabilidades de que el número aleatorio choque exactamente al mismo tiempo son muy, muy, muy bajas.
jfriend00
7
@ jfriend00, si necesita hacer eso, entonces no es un "buen número aleatorio", ni siquiera un número pseudoaleatorio decente.
Attila O.17 de
2
¿Qué significa la (r&0x3|0x8)porción / evaluación?
Kristian
¿Qué hay de agregarle un Date.now (). ToString ()?
Vitim.us
4
Existe un gran problema en su arquitectura, que no está relacionado con los UUID: el cliente puede generar intencionalmente ID en colisión. Genere ID solo mediante un sistema en el que confíe. Sin embargo, como solución alternativa, anteponga las ID generadas por el cliente con user_id, de modo que el cliente adversario / defectuoso solo pueda colisionar con ellos mismos (y manejar eso en el lado del servidor).
Dzmitry Lazerka

Respuestas:

35

Mi mejor suposición es que Math.random()está roto en su sistema por alguna razón (por extraño que parezca). Este es el primer informe que he visto de alguien que sufre colisiones.

node-uuidtiene un arnés de prueba que puede usar para probar la distribución de dígitos hexadecimales en ese código. Si se ve bien, entonces no lo es Math.random(), así que intente sustituir la implementación UUID que está utilizando en el uuid()método allí y vea si aún obtiene buenos resultados.

[Actualización: Acabo de ver el informe de Veselin sobre el error Math.random()al inicio. Dado que el problema es solo al inicio, node-uuides poco probable que la prueba sea útil. Comentaré con más detalle en el enlace devoluk.com.]

broofa
fuente
1
Gracias, voy a usar uuid.js ahora, ya que usa la criptografía fuerte del navegador si está disponible. Verá si hay colisiones.
Muxa
¿Puede proporcionar un enlace al código uuid.js al que se refiere? (lo siento, pero no estoy seguro lib que quiere decir.)
broofa
10
No tuve colisiones hasta ahora :)
Muxa
De todos modos, si es Chrome y solo al iniciarse, su aplicación podría generar y descartar una fila de, digamos, diez guías utilizando la función anterior :)
Vinko Vrsalovic
El problema es con la entropía limitada que obtienes de Math.random (). Para algunos navegadores, la entropía es tan baja como solo 41 bits en total. Llamar a Math.random () varias veces no aumentará la entropía. Si realmente desea UUID v4 únicos, debe usar un RNG criptográficamente fuerte que produzca al menos una entropía de 122 bits por UUID generado.
mlehmk
36

De hecho, hay colisiones, pero solo bajo Google Chrome. Mira mi experiencia sobre el tema aquí.

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

(Enlace roto a partir de 2019. Enlace de archivo: https://web.archive.org/web/20190121220947/http://devoluk.com/google-chrome-math-random-issue.html .)

Parece que las colisiones solo ocurren en las primeras llamadas de Math.random. Porque si simplemente ejecuta el método createGUID / testGUIDs anterior (que obviamente fue lo primero que probé), simplemente funciona sin colisiones de ningún tipo.

Entonces, para hacer una prueba completa, es necesario reiniciar Google Chrome, generar 32 bytes, reiniciar Chrome, generar, reiniciar, generar ...

Veselin Kulov
fuente
2
Eso es bastante preocupante, ¿alguien ha presentado un informe de error?
UpTheCreek
1
Especialmente como el enlace a mejores generadores de números aleatorios en javascript: baagoe.com/en/RandomMusings/javascript
Leopd
lamentablemente, dicho enlace ahora está roto :(
Gus
7
¿Alguien puede confirmar si se ha solucionado este error?
Xdrone
20

Solo para que otras personas puedan estar al tanto de esto, me encontré con una cantidad sorprendentemente grande de colisiones aparentes utilizando la técnica de generación de UUID mencionada aquí. Estas colisiones continuaron incluso después de que cambié a seedrandom para mi generador de números aleatorios. Eso me hizo arrancarme el pelo, como puedes imaginar.

Eventualmente descubrí que el problema estaba (¿casi?) Asociado exclusivamente con los robots de rastreo web de Google. Tan pronto como comencé a ignorar las solicitudes con "googlebot" en el campo de agente de usuario, las colisiones desaparecieron. Supongo que deben almacenar en caché los resultados de los scripts JS de alguna manera semiinteligente, con el resultado final de que no se puede contar con que su navegador spidering se comporte de la manera que lo hacen los navegadores normales.

Solo un FYI.

Ken Smith
fuente
2
Me encontré con el mismo problema con nuestro sistema de métricas. Estaba viendo miles de colisiones UUID usando el módulo 'node-uuid' para generar ID de sesión en el navegador. Resulta que fue Googlebot todo el tiempo. ¡Gracias!
domkck
4

Quería publicar esto como un comentario a su pregunta, pero aparentemente StackOverflow no me lo permite.

Acabo de ejecutar una prueba rudimentaria de 100,000 iteraciones en Chrome usando el algoritmo UUID que publicaste y no obtuve colisiones. Aquí hay un fragmento de código:

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);

¿Estás seguro de que no pasa nada más aquí?

usuario533676
fuente
4
Sí, también realicé algunas pruebas locales y no obtuve colisiones. Las colisiones ocurren entre UUID que se generan en las máquinas de diferentes usuarios. Es posible que deba generar algunos datos en diferentes máquinas y verificar si hay colisiones.
Muxa
2
Además, he notado que las colisiones se producen entre los UUID generados con 3 o 4 semanas de diferencia.
Muxa
Muy raro. ¿En qué plataforma estás corriendo?
user533676
1
Parece poco probable que haya una falla tan básica en Math.random () de V8, pero Chromium 11 agregó soporte para la generación de números aleatorios fuertes usando la API window.crypto.getRandomValues ​​si desea probarlo en su lugar. Ver blog.chromium.org/2011/06/… .
user533676
Se ejecuta en una combinación de Windows 7 y Windows XP.
Muxa
3

La respuesta que publicó originalmente esta solución UUID se actualizó el 2017-06-28:

Un buen artículo de los desarrolladores de Chrome que analizan el estado de la calidad PRNG de Math.random en Chrome, Firefox y Safari. tl; dr - A finales de 2015 es "bastante bueno", pero no tiene calidad criptográfica. Para abordar ese problema, aquí hay una versión actualizada de la solución anterior que usa ES6, la cryptoAPI y un poco de asistente de JS que no puedo atribuirme :

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());

Luke
fuente
0

Las respuestas aquí tratan de "¿qué está causando el problema?" (Problema de semilla aleatoria de Chrome Math.) Pero no "¿cómo puedo evitarlo?".

Si todavía está buscando cómo evitar este problema, escribí esta respuesta hace un tiempo como una versión modificada de la función de Broofa para solucionar este problema exacto. Funciona compensando los primeros 13 números hexadecimales por una parte hexadecimal de la marca de tiempo, lo que significa que incluso si Math.random está en la misma semilla, seguirá generando un UUID diferente a menos que se genere exactamente en el mismo milisegundo.

Briguy37
fuente