了解如何生成UUID

2020-10-01 03:21:00

您可能以前在项目中使用过UUID,并假定它们是唯一的。今天,我们将看一下实现的主要方面,并理解为什么UUID实际上是唯一的,尽管存在极小的重复可能性。

UUID的现代实现可以追溯到RFC4122,它引入了5种不同的方法来生成这些标识符。我们将逐一查看每个版本,稍后我们将逐步介绍版本1和版本4的实现细节。

UUID或通用唯一标识符只是一个128位的数字,用于在软件开发中唯一标识项目。它们的规范文本表示为一系列32个十六进制字符,这些字符由8-4-4-4-12形式的连字符分成五组。

在这个看似随机的十六进制字符序列中嵌入了有关UUID实现的信息。

M和N位置中的值分别唯一标识UUID版本和变量。

通过查看M位置中的值的最高有效4位来标识版本号。

变量字段确定嵌入在UUID中的信息的布局。UUID中所有其他位的解释取决于变量的值。

如今最常见的实现是变体1,其中MSB0固定为1,MSB1固定为0。这意味着考虑到我们的通配符的值-上面标有x的位-唯一可能的值是8、9、A或B。

因此,如果您曾经看到这些值位于N位置的UUID,您将知道它是常见的变体1类型。

在此版本中,通过获取当前时间戳和生成UUID的设备的某些标识属性(最常见的是MAC地址(也称为节点ID))来生成UUID。

该UUID是通过串联48位MAC地址、60位时间戳和14位“唯一”时钟序列,以及版本和变量的6个保留位来生成的,以生成唯一的UUID。

此版本中使用的时间戳是自1582年10月15日(公历改革为基督教历法的日期)以来100纳秒时间间隔的数量。

您可能熟悉Unix系统和时代以来的时间。这只是一个不同的第0天。在线有几种算法允许您将一种时间表示转换为另一种时间表示,因此我们在此不再赘述。

虽然这种实现看起来相当简单和健壮,因为它揭示了生成它的机器的MAC地址,但这种方法并不适用于所有用例,尤其是当安全性是一个主要问题时。相反,一些实现将使用来自加密安全随机数生成器的6个随机字节作为节点ID的替代。

取当前UTC时间戳的低32位。这将是我们的UUID[TimeLow]的前4个字节/8个十六进制字符。

取当前UTC时间戳的中间16位。这些将是以下2个字节/4个十六进制字符。[TimeMid]

接下来的2个字节/4个十六进制字符将把4位UUID版本与当前UTC时间戳的剩余高12位(总共60位)连接起来。[TimeHighAndVersion]。

现在,接下来的1-3位将指定UUID版本的变体。剩余的比特将包含时钟序列,该时钟序列意在为该实现贡献一些小量的随机性。这样做有助于避免在多个UUID生成器在同一系统上运行、UUID生成器的系统时钟向后设置或系统时钟前进不够快的情况下发生冲突。[ClockSequenceHiAndRes&;&;ClockSequenceLow]。

最后6个字节/12个十六进制字符/48位是“节点ID”,它通常是发布设备的MAC地址。[节点ID]。

由于此实现依赖于时钟,因此我们需要处理一些边缘情况。首先,为了最大限度地减少系统间的相关性,时钟序列默认为随机数-这在系统的生命周期中只会发生一次。这具有允许我们支持可以在系统之间移动的节点标识符的附加好处,因为时钟序列的初始值与节点标识符完全不可知。

请记住,时钟序列的主要目的是在方程中引入一些随机性。分配给时钟序列的位帮助我们扩展时间戳,并解决在处理器时钟前进之前生成多个UUID的情况。这可以帮助我们避免在时钟时间倒退(设备断电)或节点ID更改时可能创建重复项。如果时钟被倒置,或者可能已经被倒置(例如,当系统断电时),并且UUID生成器不能确定没有生成具有大于时钟被设置的值的时间戳的UUID,则时钟序列必须被改变。如果时钟序列的前一个值是已知的,则只能递增;否则应将其设置为随机或高质量伪随机值。

该版本与版本1之间的主要区别在于,该实现使用特定于系统的某些标识符来代替通过使用时钟序列的最低有效位而产生的“随机性”。这个值通常只是当前用户的ID。这个版本不太常见,只与版本1有很小的偏差,所以我们不会进一步研究它。

如果您希望名称空间内的信息或更一般的“可命名”信息具有唯一标识符,则UUID版本3和版本5是您的首选选项。

它们会将任何“可命名”实体(网站、DNS信息、纯文本等)编码到UUID值中。这里的主要问题是,对于相同的名称空间和文本,生成的UUID将是相同的。

让NAMESPACE=“digitalbunker.dev”let NamespaceUuid=uuid3(.DNS,NAMESPACE)//Ex:uuid3(NAMESPACEUUID,“/CATEGORY/Things-You-Short-Know-1/”)4896c91b-9e61-3129-87b6-8aa299028058UUID3(namespaceUUID,“/CATEGORY/Things-You-Short-Know-2/”)29be0ee3-fe77-331e-a1bf-9494ec18c0baUUID3(namespaceUUID,“/CATEGORY/Things-You-Short-Well-3/”)33b06619-1ee7-3db5-827d-0dc85df1f1f759。

在此实现中,名称空间的UUID被转换为字节字符串,与输入名称连接,然后使用MD5进行散列,从而产生用于UUID的128位。然后,我们将覆盖其中的一些位,以准确反映版本和变体信息,其余位保持不变。

同样重要的是要理解,名称空间和输入的名称都不能从UUID生成。这是一个单向的行动。这里唯一的例外是,如果攻击者知道其中一个值(名称空间或文本),则使用暴力方法。

对于版本3和版本5,只要您使用相同的输入,生成的UUID就是确定性的。

正如我们现在看到的,为版本和变体信息保留了6位,让我们可以自由决定122位。此版本只需生成全部128个随机位,然后作为次要步骤填充版本和变量信息的值。

这一变种的UUID在很大程度上依赖于正在使用的PRNG(伪随机数生成器)的质量。如果PRNG缺少复杂的算法或正确的种子和初始化值,则重复的可能性可能会增加。要更好地理解计算机是如何生成随机数的,请查看我以前的文章。

版本4是在现代编程语言中最常见的实现。

现在,我们需要用正确的版本和变体信息覆盖这些位中的一些位,取第7个字节并执行与0x0F的AND操作,以清除高位半字节。然后,将其与0x40进行或运算,将版本号设置为4。

接下来,取第9个字节,与0x3F执行AND运算,然后与0x80执行OR。

将128位转换为十六进制表示,并插入连字符以实现规范文本表示。

除了这里使用SHA-1散列算法代替MD5之外,此版本与版本3没有什么不同。此版本优先于版本3(SHA-1>;MD5)。

UUID的显著好处之一是,它们的独特性不依赖于中央机构或不同系统之间的协调。任何人都可以合理地保证不存在重复值,并且可以想象将来也不会创建UUID。

这还有一个额外的好处,即允许将独立各方生成的UUID合并到单个数据库中,或者以极小的复制/冲突概率跨数据库移动。

由于这种唯一性,您可以将UUID用作数据库中的主键、上载文件的唯一文件名、任何Web资源的唯一名称,或者允许供应商创建和注册UUID,而不需要中央机构。然而,这是一把双刃剑。由于缺乏中央机构,不可能跟踪以前发布了哪些UUID。

还有一些缺点需要解决。虽然这种固有的随机性有助于安全性,但它使调试等问题变得复杂。此外,在某些情况下,UUID可能是过度杀伤力。例如,使用128位来唯一标识某些本身大小可能小于128位的数据是没有意义的。

看一下这些实现,似乎如果有足够的时间,您最终会重复一个值。特别是在依赖于随机数的版本4的情况下。然而,在实践中,为了获得复制的机会而需要生成UUID的数量和频率是完全不切实际的。

如果在接下来的100年中,您每秒生成10亿个UUID,则仅创建一个副本的可能性约为50%。当然,这是假设在生成UUID时使用的PRNG在混合中引入了足够的熵(“真正的随机性”),否则重复计数的概率会更高。在一个稍微具体一点的示例中,如果要生成10,000,000,000,000[10万亿]个UUID,则2个UUID相同的可能性为0.00000006%。

在版本1的情况下,时钟只会滚动到公元360年。所以,除非你计划让你的服务再运行1583年,否则你在这条战线上也是安全的。

重复的可能性仍然存在,一些系统确实会尝试说明这一点,但对于绝大多数用例,可以将UUID视为真正唯一的UUID。如果你仍然需要更令人信服的证据,这里有一个简单的可视化/证据来证明在实践中发生碰撞的可能性有多大。

希望您喜欢这篇文章!注册我的时事通讯,当新文章发布时会收到通知!