将UUID转换为短代码(仅使用前8个字符)是否安全?

18
我们在数据库中使用UUID作为主键(由php生成,存储在mysql中)。问题在于,当有人想要编辑某些内容或查看其个人资料时,在URL的末尾会出现一个巨大、可怕、丑陋的UUID字符串。(edit?id=.....)
如果我们只使用第一组连字符之前的前8个字符,这样做是否安全(即:仍然唯一)?
如果这样不安全,是否有一种将其转换为短一些的东西以便用作URL的方法,并可以将其转回十六进制字符以用作查找?我知道可以对其进行base64编码以将其缩短到22个字符,但是否有更短的方式?
编辑 我已经阅读了这个问题,它建议使用base64。还有其他更短的方法吗?

你希望UUID除了前8个字符以外的所有字符都是多余的吗?它们存在是为了使其唯一。无论如何,如果只有8个字符而不是22个字符,你真的认为这会让用户体验更友好吗?我不会花时间担心这个问题。我在地址栏中看到过更疯狂的URI,但这并不影响网站的可用性。 - webbiedave
我在思考类似于如何缩短MD5哈希值的方法(在某个地方读到过但是忘记了在哪里),因为对于某个子字符串来说,字符具有均匀分布。 - helloandre
我明白了。加密哈希函数应该具有可接受的碰撞发生率,而仅仅截断输出字符串会增加这种可能性。你的情况不能承受任何“碰撞”的机会(一个 ID 指向多个记录)。 - webbiedave
6个回答

17

缩短UUID会增加碰撞的概率,虽然可以这样做,但这是一个不好的想法。仅使用8个字符意味着只有4个字节的数据,因此一旦您拥有约2^16个ID就可能会发生冲突 - 远非理想。

最好的选择是获取UUID的原始字节(而不是十六进制表示),并使用base64进行编码。或者,如果您的用户并不关心URL中的内容,那么也不必过于担心。


2
就算是在2^16条记录之前,由于生日悖论的存在,你也应该预料到会发生碰撞。 - Christopher Swasey
3
记录一下,通常4个字节等于32位,因此应该是2^32而不是2^16。 - Gonzalo Serrano

10

不要删减UUID中的任何一位:您无法控制生成它的算法,有多种可能的实现方式,算法的实现可能会发生变化(例如:随着您使用的PHP版本的更改而改变)。

如果你问我,在地址栏中看到UUID并不可怕或难懂,甚至简单的谷歌搜索"UUID"也会产生更糟糕的URL,大家已经习惯了看谷歌的URL!

如果你想要更漂亮的URL,看看这篇stackoverflow.com文章的地址栏。他们使用了文章ID,并跟随问题的标题。只有ID部分是相关的,其他所有内容都是为了让读者更容易阅读(试试吧,你可以删除ID后面的任何内容,甚至可以替换为垃圾文本 - 没关系)。


3

截断uuid并不安全。而且,它们被设计为全球唯一的,所以你缩短它们是没有好运气的。你最好的选择是为每个用户分配一个唯一的数字,或让用户选择一个自定义(唯一)字符串(比如用户名或昵称),可以在你的脚本中解码。因此,你可以有edit?id=.... 或edit?name=blah,然后在你的脚本中将名字解码成uuid。


选择自定义名称是可行的,但并不总是编辑用户配置文件。它可能是用户创建的东西,除了UUID之外没有强制唯一性(比如相册)。 - helloandre

1

这取决于你如何生成UUID - 如果你使用PHP的uniqid,那么右侧的数字更加“独特”。然而,如果你要截断数据,那就没有真正的保证它会是唯一的。

不管怎样,我认为这是一个相对不太理想的方法-你是否有办法在查询字符串中使用唯一(最好是有意义的)文本参考字符串,而不是ID?(在没有更多问题领域知识的情况下很难确定,但我始终认为这是更好的方法,即使不考虑SEO等因素。)

如果你采用了这种方法,你也可以让MySQL生成唯一的ID,这可能比在PHP中处理要合理得多。


1
如果您担心在URL中使用UUID会吓到用户,为什么不将其写入隐藏的表单字段中呢?

我想找到在裸露的URL和在URL中有一些视觉提示来指示你正在编辑的内容之间的折衷方案。不过最终可能会采用这种方法。 - helloandre

0
老问题,但我认为应该提到“短标识符”是一种常见做法,用于呈现更加人性化的代码,而不是完全替代完整的标识符。此外,对于任何标识符(无论是数字、UUID、SHA还是其他类型),都适用这一点。
正如其他答案已经提到的,您应该始终将完整的UUID作为事实上的记录键。
短标识符的实现因需求而异,但有两个共同点:
1. 处理短标识符的接口/系统不会默默解决歧义。(请注意,碰撞可能发生,但根据上下文可能不会产生歧义) 2. 用户可以透明地选择使用短标识符或完整标识符。
以下是一些常见的实现方式:
为每个资源生成两个ID,其中一个ID是递增整数。这是我见过的最简单的方法,它避免了碰撞,并且在最终用户界面中只使用基于整数的ID。
允许使用原始ID的任何短格式,但当短ID存在歧义时返回错误或返回所有匹配项。Git提交就是一个例子,但它们使用SHA而不是UUID。
使用固定数量的字符提取短ID,但在发生碰撞时增加字符数量,并使用短ID的长度作为解决碰撞的信息。如果上下文允许使旧记录失效,那么可以回到较短的ID。
使用固定数量的字符作为短ID,并结合上下文信息来减少歧义,例如将搜索范围缩小到用户被允许访问的资源。请谨慎使用此方法,如果有效资源的数量一直增加到某个阈值(取决于短ID的字符数量),新记录将始终与旧记录发生冲突。适用于此方法的一个例子是保险记录,您可以预期在一段时间后保险将过期,从而使系统能够优雅地处理冲突:输入前N位数字以搜索活动保险,但进行单独的搜索需要所有数字以搜索所有或非活动保险。
作为一个额外的奖励,如果你只想显示字母代码而不是数字,你可以使用类似hashids这样的库将基于数字的ID编码为代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接