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?

Muxa
la source
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.]

Broofa
la source
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
Je n'ai
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 ...

Veselin Kulov
la source
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
2
web.archive.org/web/20120503063359/http
Beni Cherniavsky-Paskin
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.

Ken Smith
la source
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?

user533676
la source
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());

Luke
la source
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.

Briguy37
la source