在 JavaScript 中生成 UUID 时发生冲突

Posted

技术标签:

【中文标题】在 JavaScript 中生成 UUID 时发生冲突【英文标题】:Collisions when generating UUIDs in JavaScript 【发布时间】:2011-10-17 22:14:39 【问题描述】:

这与this question 有关。我正在使用来自this answer 的以下代码在 javascript 中生成 UUID:

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

此解决方案似乎运行良好,但我遇到了冲突。这是我所拥有的:

在 Google Chrome 中运行的网络应用程序。 16 个用户。 这些用户在过去两个月内生成了大约 4000 个 UUID。 我遇到了大约 20 次冲突 - 例如,今天生成的新 UUID 与大约两个月前(不同的用户)相同。

是什么导致了这个问题,我该如何避免?

【问题讨论】:

结合一个好的随机数和当前时间(以毫秒为单位)。随机数在同一时间发生碰撞的几率真的非常非常低。 @jfriend00 如果你需要这样做,那么它不是一个“好的随机数”,甚至不是一个像样的伪随机数。 (r&0x3|0x8) 部分的含义/评估是什么? 追加一个 Date.now().toString() 怎么样? 您的架构中有一个大问题,与 UUID 无关——客户端可能故意生成冲突的 ID。仅由您信任的系统生成 ID。不过,作为一种解决方法,在客户端生成的 ID 前面加上 user_id,这样攻击者/故障客户端只能与自己发生冲突(并在服务器端进行处理)。 【参考方案1】:

我最好的猜测是Math.random() 在你的系统上由于某种原因被破坏了(听起来很奇怪)。这是我第一次看到有人发生碰撞的报告。

node-uuid 有一个test harness,您可以使用它来测试该代码中十六进制数字的分布。 如果看起来没问题,那么它不是 Math.random(),那么请尝试将您正在使用的 UUID 实现替换为那里的 uuid() 方法,看看您是否仍然可以获得良好的结果。

[更新:刚刚看到 Veselin's report 关于 Math.random() 在启动时的错误。由于问题仅在启动时出现,node-uuid 测试不太可能有用。我会在 devoluk.com 链接上更详细地评论。]

【讨论】:

谢谢,我现在使用 uuid.js,因为它使用浏览器的强加密(如果可用)。看看有没有碰撞。 您能否提供指向您所指的 uuid.js 代码的链接? (抱歉,不确定您指的是哪个库。) 到目前为止没有碰撞 :) 无论如何,如果它是 Chrome 并且只有在启动时,您的应用程序可以使用上述函数生成并丢弃一行,比如说,十个 guid :) 问题在于您从 Math.random() 获得的熵有限。对于某些浏览器,熵总共只有 41 位。多次调用 Math.random() 不会提高熵。如果你真的想要唯一的 v4 UUID,你需要使用加密强的 RNG,每个生成的 UUID 至少产生 122 位熵。【参考方案2】:

确实存在冲突,但仅限于谷歌浏览器。在 Google Chrome random number generator issue

中查看我对该主题的体验

似乎冲突只发生在 Math.random 的前几次调用中。因为如果您只运行上面的 createGUID / testGUIDs 方法(这显然是我尝试的第一件事),它就可以正常工作而不会发生任何冲突。

所以要进行完整的测试需要重启谷歌浏览器,生成 32 字节,重启 Chrome,生成,重启,生成等。

【讨论】:

这很令人担忧 - 有人提出错误报告吗? 特别喜欢javascript中更好的随机数生成器的链接:baagoe.com/en/RandomMusings/javascript 很遗憾,该链接现在已损坏:( web.archive.org/web/20120503063359/http://baagoe.com/en/… 谁能确认这个错误是否已经解决?【参考方案3】:

只是为了让其他人知道这一点 - 我使用此处提到的 UUID 生成技术遇到了大量明显的冲突。即使在我为我的随机数生成器切换到seedrandom 之后,这些冲突仍在继续。正如你想象的那样,这让我把头发扯掉了。

我最终发现这个问题(几乎?)完全与 Google 的网络爬虫机器人有关。一旦我开始忽略用户代理字段中带有“googlebot”的请求,冲突就消失了。我猜他们必须以某种半智能的方式缓存 JS 脚本的结果,最终导致无法指望他们的爬虫浏览器表现得像普通浏览器那样。

仅供参考。

【讨论】:

我们的指标系统遇到了同样的问题。使用“node-uuid”模块在浏览器中生成会话 ID 时看到了数以千计的 UUID 冲突。原来它一直是googlebot。谢谢!【参考方案4】:

我刚刚使用您发布的 UUID 算法在 Chrome 中对 100,000 次迭代进行了初步测试,但没有遇到任何冲突。这是一个代码sn-p:

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

【讨论】:

是的,我也进行了一些本地测试,没有发生碰撞。在不同用户的机器上生成的 UUID 之间会发生冲突。我可能需要在不同的机器上生成一些数据并检查冲突。 另外,我注意到 UUID 之间的冲突是相隔 3-4 周产生的。 很奇怪。你在哪个平台上运行? V8 的 Math.random() 中似乎不太可能存在如此基本的缺陷,但如果您想尝试一下,Chromium 11 增加了对使用 window.crypto.getRandomValues API 生成强随机数的支持.见blog.chromium.org/2011/06/…。 在 Windows 7 和 Windows XP 的组合上运行。【参考方案5】:

最初发布此UUID解决方案的The answer于2017-06-28更新:

good article from Chrome developers 讨论 Chrome、Firefox 和 Safari 中 Math.random PRNG 质量的状态。 tl; dr - 截至 2015 年底,它“相当不错”,但不是加密质量。为了解决这个问题,这里是使用 ES6、crypto API 和 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());

【讨论】:

【参考方案6】:

这里的答案涉及“导致问题的原因是什么?” (Chrome Math.random 种子问题)但不是“我怎样才能避免它?”。

如果您仍在寻找避免此问题的方法,我不久前写了this answer 作为对 Broofa 功能的修改,以解决这个确切的问题。它通过将前 13 个十六进制数字偏移时间戳的十六进制部分来工作,这意味着即使 Math.random 位于同一个种子上,它仍然会生成不同的 UUID,除非在完全相同的毫秒内生成。

【讨论】:

以上是关于在 JavaScript 中生成 UUID 时发生冲突的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SAP UI5 代码中生成 UUID? [复制]

如何在ios中生成UUID

如何在 Python 中生成可重现(带有种子)的随机 UUID

如何在swift中生成设备特定的唯一uuid

如何在 C++ 中生成 UUID,而不使用 boost 库?

SQL语句中生成UUID方法