Collisions when generating UUIDs in JavaScript
Asked Answered
Y

6

98

This relates to this question. I am using the code below from this answer to generate a 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);
});

This solution appeared to be working fine, but I am getting collisions. Here's what I have:

  • A web application running in Google Chrome.
  • 16 users.
  • about 4000 UUIDs have been generated in the past two months by these users.
  • I got about 20 collisions - e.g., a new UUID generated today was the same as about two months ago (different user).

What is causing this issue and how can I avoid it?

Yang answered 2/8, 2011 at 3:22 Comment(6)
Combine a good random number with the current time (in milliseconds). The odds of the random number colliding at exactly the same time are really, really, really low.Invest
@Invest if you need to do that then it is not a "good random number", not even a decent pseudo-random number.Ilsa
what does the (r&0x3|0x8) portion mean / evaluation to?Avram
What about appending a Date.now().toString() to it?Windpollinated
There's a big problem in your architecture, unrelated to UUIDs -- client may intentionally generate colliding IDs. Generate IDs only by a system you trust. As a workaround, though, prepend client-generated IDs with user_id, so that adversary/faulty client can only collide with themselves (and handle that on server side).Alicealicea
What about using a timestamp to scramble data when generating the UUIDs (eg : by calling new Date()) ? Would it help to reduce the collisions on Chrome ?Firstrate
P
37

My best guess is that Math.random() is broken on your system for some reason (bizarre as that sounds). This is the first report I've seen of anyone getting collisions.

node-uuid has a test harness that you can use to test the distribution of hex digits in that code. If that looks okay then it's not Math.random(), so then try substituting the UUID implementation you're using into the uuid() method there and see if you still get good results.

[Update: Just saw Veselin's report about the bug with Math.random() at startup. Since the problem is only at startup, the node-uuid test is unlikely to be useful. I'll comment in more detail on the devoluk.com link.]

Pagepageant answered 25/8, 2011 at 4:49 Comment(5)
Thanks, I'm going with uuid.js now, since it uses browser's strong crypto if available. Will see if there are any collisions.Yang
can you provide a link to the uuid.js code you're referring to? (sorry, not sure which lib you mean.)Pagepageant
Had no collisions so far :)Yang
Anyway, if it's Chrome and only when starting, your app could generate and discard a row of, say, ten guids using the above function :)Ai
The problem is with limited entropy you get from Math.random(). For some browsers the entropy is as low as just 41 bits all together. Calling Math.random() multiple times won't raise the entropy. If you really want unique v4 UUIDs you need to use a cryptographically strong RNG that produces at least 122bit entropy per UUID generated.Kunzite
A
36

Indeed there are collisions, but only under Google Chrome. Check out my experience on the topic in Google Chrome random number generator issue

It seems like collisions only happen on the first few calls of Math.random. Because if you just run the createGUID / testGUIDs method above (which obviously was the first thing I tried), it just works without any collisions whatsoever.

So to make a full test one needs to restart Google Chrome, generate 32 byte, restart Chrome, generate, restart, generate, etc.

Arnaldo answered 24/8, 2011 at 13:43 Comment(6)
That's pretty worrying - has anyone raised a bug report?Lohengrin
Especially like the link to better random number generators in javascript: baagoe.com/en/RandomMusings/javascriptDissentious
sadly, said link is now broken :(Tendon
web.archive.org/web/20120503063359/http://baagoe.com/en/…Premiere
Can any one confirm if this bug has been addressed?Mortgagor
Chrome isn't broken. Math.random() has no requirements whatsoever about entropy. And even if the PRNG was seeded, it shouldn't be used for cryptography.Kunzite
H
21

Just so that other folks can be aware of this - I was running into a surprisingly large number of apparent collisions using the UUID generation technique mentioned here. These collisions continued even after I switched to seedrandom for my random number generator. That had me tearing my hair out, as you can imagine.

I eventually figured out that the problem was (almost?) exclusively associated with Google's web crawler bots. As soon as I started ignoring requests with "googlebot" in the user-agent field, the collisions disappeared. I'm guessing that they must cache the results of JS scripts in some semi-intelligent way, with the end result that their spidering browser can't be counted on to behave the way that normal browsers do.

Just an FYI.

Hydrogenous answered 14/6, 2014 at 20:37 Comment(1)
Ran into the same issue with our metrics system. Was seeing thousands of UUID collisions using the 'node-uuid' module to generate session IDs in browser. Turns out it was googlebot all along. Thanks!Sample
A
4

I just ran a rudimentary test of 100,000 iterations in Chrome using the UUID algorithm you posted, and I didn't get any collisions. Here's a code snippet:

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);
Ambassadress answered 2/8, 2011 at 12:20 Comment(5)
Yes, I ran some local tests too and got no collisions. Collisions happen between UUIDs what are generated on different user's machines. I might need to generate some data on different machines and check for collisions.Yang
Also, i've noticed that collisions are between UUIDs generated 3-4 weeks apart.Yang
Very odd. What platform are you running on?Ambassadress
It seems unlikely that there's a flaw so basic in V8's Math.random(), but Chromium 11 added support for strong random number generation using the window.crypto.getRandomValues API if you want to try it instead. See blog.chromium.org/2011/06/….Ambassadress
Running on combination of Windows 7 and Windows XP.Yang
F
4

The answer that originally posted this UUID solution was updated on 2017-06-28:

A good article from Chrome developers discussing the state of Math.random PRNG quality in Chrome, Firefox, and Safari. tl;dr - As of late-2015 it's "pretty good", but not cryptographic quality. To address that issue, here's an updated version of the above solution that uses ES6, the crypto API, and a bit of JS wizardy I can't take credit for:

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());
Febricity answered 3/1, 2018 at 18:40 Comment(0)
T
1

The answers here deal with "what's causing the issue?" (Chrome Math.random seed issue) but not "how can I avoid it?".

If you are still looking for how to avoid this issue, I wrote this answer a while back as a modified take on Broofa's function to get around this exact problem. It works by offsetting the first 13 hex numbers by a hex portion of the timestamp, meaning that even if Math.random is on the same seed it will still generate a different UUID unless generated at the exact same millisecond.

Torus answered 26/7, 2019 at 19:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.