Collisions lors de la génération d'UUID en JavaScript?


94

Cela concerne cette question . J'utilise le code ci-dessous à partir de cette réponse pour générer l'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);
});

Cette solution semble fonctionner correctement, mais j'obtiens des collisions. Voici ce que j'ai:

  • Une application Web fonctionnant dans Google Chrome.
  • 16 utilisateurs.
  • environ 4000 UUID ont été générés au cours des 2 derniers mois par ces utilisateurs.
  • J'ai eu environ 20 collisions - par exemple, le nouvel UUID généré aujourd'hui était le même qu'il y a environ 2 mois (utilisateur différent).

Quelle est la cause de ce problème et comment puis-je l'éviter?


2
Combinez un bon nombre aléatoire avec l'heure actuelle (en millisecondes). Les chances que le nombre aléatoire entre en collision exactement au même moment sont vraiment, vraiment, vraiment faibles.
jfriend00

7
@ jfriend00 si vous devez faire cela, alors ce n'est pas un "bon nombre aléatoire", pas même un nombre pseudo-aléatoire décent.
Attila O.

2
à quoi signifie la (r&0x3|0x8)portion / évaluation?
Kristian

Pourquoi ne pas y ajouter Date.now (). ToString ()?
Vitim.us

4
Il y a un gros problème dans votre architecture, sans rapport avec les UUID - le client peut générer intentionnellement des ID en collision. Générez des identifiants uniquement par un système de confiance. Cependant, comme solution de contournement, ajoutez au début les ID générés par le client avec user_id, de sorte que l'adversaire / client défectueux ne puisse entrer en collision avec lui-même (et le gérer côté serveur).
Dzmitry Lazerka

Réponses:


35

Ma meilleure supposition est que Math.random()votre système est cassé pour une raison quelconque (aussi étrange que cela puisse paraître). C'est le premier rapport que j'ai vu sur quelqu'un qui aurait des collisions.

node-uuida un faisceau de test que vous pouvez utiliser pour tester la distribution des chiffres hexadécimaux dans ce code. Si cela semble correct, alors ce n'est pas le cas Math.random(), alors essayez de remplacer l'implémentation UUID que vous utilisez dans la uuid()méthode et voyez si vous obtenez toujours de bons résultats.

[Mise à jour: Je viens de voir le rapport de Veselin sur le bogue Math.random()au démarrage. Étant donné que le problème ne se situe qu'au démarrage, il node-uuidest peu probable que le test soit utile. Je commenterai plus en détail le lien devoluk.com.]


1
Merci, je vais maintenant avec uuid.js, car il utilise la crypto forte du navigateur si disponible. Va voir s'il y a des collisions.
Muxa

pouvez-vous fournir un lien vers le code uuid.js auquel vous faites référence? (désolé, je ne sais pas de quelle lib vous parlez.)
broofa

10

Quoi qu'il en soit, s'il s'agit de Chrome et uniquement au démarrage, votre application pourrait générer et supprimer une rangée de, disons, dix guides en utilisant la fonction ci-dessus :)
Vinko Vrsalovic

Le problème est avec l'entropie limitée que vous obtenez de Math.random (). Pour certains navigateurs, l'entropie est aussi faible que 41 bits au total. Appeler Math.random () plusieurs fois ne soulèvera pas l'entropie. Si vous voulez vraiment des UUID v4 uniques, vous devez utiliser un RNG cryptographiquement puissant qui produit une entropie d'au moins 122 bits par UUID généré.
mlehmk

36

En effet il y a des collisions mais uniquement sous Google Chrome. Découvrez mon expérience sur le sujet ici

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

(Lien rompu à partir de 2019. Lien d'archive: https://web.archive.org/web/20190121220947/http://devoluk.com/google-chrome-math-random-issue.html .)

Il semble que les collisions ne se produisent que lors des premiers appels de Math.random. Parce que si vous exécutez simplement la méthode createGUID / testGUIDs ci-dessus (ce qui était évidemment la première chose que j'ai essayée), cela fonctionne simplement sans aucune collision.

Donc, pour faire un test complet, il faut redémarrer Google Chrome, générer 32 octets, redémarrer Chrome, générer, redémarrer, générer ...


2
C'est assez inquiétant - est-ce que quelqu'un a signalé un bug?
UpTheCreek

1
Surtout comme le lien vers de meilleurs générateurs de nombres aléatoires en javascript: baagoe.com/en/RandomMusings/javascript
Leopd

malheureusement, ledit lien est maintenant rompu :(
Gus


7
Quelqu'un peut-il confirmer si ce bogue a été corrigé?
Xdrone

20

Juste pour que les autres puissent en être conscients, je rencontrais un nombre étonnamment élevé de collisions apparentes en utilisant la technique de génération d'UUID mentionnée ici. Ces collisions ont continué même après que je sois passé à seedrandom pour mon générateur de nombres aléatoires. Cela m'a fait arracher les cheveux, comme vous pouvez l'imaginer.

J'ai finalement compris que le problème était (presque?) Exclusivement associé aux robots d'exploration du Web de Google. Dès que j'ai commencé à ignorer les requêtes avec "googlebot" dans le champ user-agent, les collisions ont disparu. Je suppose qu'ils doivent mettre en cache les résultats des scripts JS de manière semi-intelligente, avec pour résultat final que leur navigateur spidering ne peut pas être compté pour se comporter comme le font les navigateurs normaux.

Juste un FYI.


2
Ran dans le même problème avec notre système de métriques. A vu des milliers de collisions UUID en utilisant le module 'node-uuid' pour générer des identifiants de session dans le navigateur. Il s'est avéré que c'était toujours googlebot. Merci!
domkck

4

Je voulais publier ceci comme un commentaire à votre question, mais apparemment, StackOverflow ne me le permettra pas.

Je viens de lancer un test rudimentaire de 100000 itérations dans Chrome à l'aide de l'algorithme UUID que vous avez publié et je n'ai eu aucune collision. Voici un extrait de code:

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

Etes-vous sûr qu'il ne se passe pas autre chose ici?


4
Oui, j'ai également effectué des tests locaux et je n'ai eu aucune collision. Des collisions se produisent entre les UUID générés sur les machines de différents utilisateurs. Je pourrais avoir besoin de générer des données sur différentes machines et de vérifier les collisions.
Muxa

2
De plus, j'ai remarqué que les collisions se produisent entre les UUID générés à 3-4 semaines d'intervalle.
Muxa

Très étrange. Sur quelle plateforme utilisez-vous?
user533676

1
Il semble peu probable qu'il y ait une faille aussi basique dans Math.random () de V8, mais Chromium 11 a ajouté la prise en charge de la génération de nombres aléatoires puissants à l'aide de l'API window.crypto.getRandomValues ​​si vous voulez l'essayer à la place. Voir blog.chromium.org/2011/06/… .
user533676

Fonctionnant avec une combinaison de Windows 7 et Windows XP.
Muxa

3

La réponse qui a initialement publié cette solution UUID a été mise à jour le 28/06/2017:

UNE bon article des développeurs de Chrome sur l'état de la qualité Math.random PRNG dans Chrome, Firefox et Safari. tl; dr - À la fin de 2015, c'est "assez bon", mais pas de qualité cryptographique. Pour résoudre ce problème, voici une version mise à jour de la solution ci-dessus qui utilise ES6, l' cryptoAPI et un peu d'assistant JS dont je ne peux pas m'attribuer le mérite :

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

Les réponses ici traitent de "quelle est la cause du problème?" (Problème de graine Chrome Math.random) mais pas "comment puis-je l'éviter?".

Si vous cherchez toujours comment éviter ce problème, j'ai écrit cette réponse il y a quelque temps en tant que version modifiée de la fonction de Broofa pour contourner ce problème exact. Il fonctionne en décalant les 13 premiers nombres hexadécimaux par une partie hexadécimale de l'horodatage, ce qui signifie que même si Math.random est sur la même graine, il générera toujours un UUID différent à moins d'être généré exactement à la même milliseconde.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.