I am looking at a seemingly popular piece of JavaScript code to generate a UUID which is supposed to be a 128-bit number:
function uuidv4() {
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);
});
}
This uses the browser's Math.random()
. To dissect this further, it seems to be mostly replacing each x
character in the string with a separate call to the Math.random API to create a hex digit (4 bits) e.g.:
function getRandomHexChar() {
let randomChar = Math.random(); // 0.6429364007765519
randomChar = randomChar * 16; // 10.28698241242483
randomChar = randomChar | 0; // 10
randomChar = randomChar.toString(16); // a
return randomChar;
}
For our application I assume we absolutely need the UUID to be unique or probably bad things could happen if it is repeated. However the thing I would like to know is if it needs to use a cryptographically secure PRNG to guarantee it to be unique?
Apparently Math.random once returned random numbers limited to $2^{32}$. It is a bit better in Chrome, Firefox and Safari now though, they are able to return numbers limited to $2^{128}$ (but it now uses xorshift128+ algorithm which is not cryptographically secure). Also this is certainly not 'all' browsers so maybe it's safe to estimate Math.random only gives $2^{32}$ bits of entropy.
So I guess my question really boils down to this: With repeated calls to Math.random (i.e. a non-cryptographically secure 128 bit RNG or with perhaps $2^{32}$ bits of entropy) like this e.g. getRandomHexChar() + getRandomHexChar() + ...
to concatenate 4 bits of pseudo randomness at a time until you get a 128 bit number, will this really give a safe unique UUID of 128 bits? Or is the entropy in that resulting UUID much lower?
Math.random
. Request tracing IDs were being generated client side, and with 40k concurrent users we did see collisions within days of launching the feature. Likely becauseMath.random
is seeded with the system time in many browsers. Switching tocrypto.getRandomValues
for supporting browsers fixed the issue. $\endgroup$