在 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 时发生冲突的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Python 中生成可重现(带有种子)的随机 UUID