生成v5 UUID。什么是名称和名称空间?


125

我已经阅读了该man页面,但是我不理解其用途namenamespace目的。

对于版本3和版本5 UUID,必须指定其他命令行参数名称空间和名称。名称空间可以是字符串表示形式的UUID,也可以是内部预定义名称空间UUID的标识符(当前已知为“ ns:DNS”,“ ns:URL”,“ ns:OID”和“ ns:X500”)。名称是任意长度的字符串。

命名空间:

名称空间可以是字符串表示形式的UUID,也可以是

这是否意味着我需要将其(UUID v4)存储在与生成的UUID v5相关的位置?无论哪种情况,为什么都不会自动完成?

名称是任意长度的字符串。

name一个完全随机的字符串?那么它的目的是什么?可以从UUID v5解码吗?

Answers:


106

名称和名称空间可用于创建(很可能是)唯一UUID的层次结构。

粗略地说,类型3或类型5的UUID是通过将名称空间标识符和名称哈希在一起而生成的。类型3 UUID使用MD5,类型5 UUID使用SHA1。只有128位可用,而5位用于指定类型,因此所有散列位都不会将其放入UUID。(此外,MD5也被认为是加密破坏的,而SHA1则处于末尾状态,因此不要使用它来验证需要“非常安全”的数据)。就是说,它为您提供了一种创建可重复/可验证的“哈希”函数的方法,该函数将可能的层次结构名称映射到概率唯一的128位值上,从而可能像层次结构哈希或MAC一样起作用。

假设您有一个(键,值)存储,但是它仅支持一个名称空间。您可以使用类型3或类型5 UUID生成大量不同的逻辑名称空间。首先,为每个名称空间创建一个根UUID。只要您将它存放在某个地方,它就可以是1型(主机+时间戳)或4型(随机)UUID。或者,您可以为根创建一个随机UUID(或使用nullUUID:00000000-0000-0000-0000-000000000000作为根),然后使用“ uuid -v5 $ROOTUUID $NAMESPACENAME” 为每个名称空间创建可重现的UUID 。现在,您可以使用“”为名称空间中的键创建唯一的UUIDuuid -v5 $NAMESPACEUUID $KEY可以将这些UUID扔到单个键值存储中,避免冲突的可能性很高。可以以递归方式重复此过程,因此,例如,如果与UUID键相关联的“值”又表示某种逻辑“名称空间” ”(例如存储桶,容器或目录),然后可以依次使用其UUID生成更多分层的UUID。

生成的类型3或类型5 UUID保留名称空间ID和名称内名称空间(键)的(部分)哈希。消息MAC不再保存名称空间UUID,而消息MAC保留了来自其编码的消息的内容。从uuid算法的角度来看,该名称是一个“任意”(八位位组)字符串。但是,其含义取决于您的应用程序。它可以是逻辑目录中的文件名,对象存储中的对象ID等。

尽管这对于中等数量的名称空间和键很有效,但是如果您希望以非常高的概率获得数量众多的唯一键,则最终会耗尽精力。维基百科上有关生日问题(又名生日悖论)的条目包括一个表,该表给出了不同数量的键和表大小至少发生一次碰撞的概率。对于128位,以这种方式对260亿个密钥进行哈希处理时发生冲突的可能性p=10^-18(可以忽略不计),但是对于26万亿个密钥,则至少发生一次冲突的概率为p=10^-12(十亿分之一),而对26*10^15密钥进行哈希处理则可以提高发生冲突的可能性。至少发生一次碰撞p=10^-6(百万分之一)。调整5个编码UUID类型的位,它会更快地用完,因此一万亿个键大约有1万亿分之一的机会发生一次冲突。

有关概率表,请参见http://en.wikipedia.org/wiki/Birthday_problem#Probability_table

有关UUID编码的更多详细信息,请参见http://www.ietf.org/rfc/rfc4122.txt


2
在层次结构的某个特定级别,我是否可以使用UUIDv5作为名称空间,并使用UUIDv4作为随机密钥,以确保数据本身(由此GUID标识)中的冲突不会增加碰撞UUID的机会?我应该知道任何性能问题吗?
ermik '18年

我是这个概念的新手,并对您所谈论的层次结构感到困惑。我在哪里可以看到,等等...一旦我坚持解释,可能会变得有些清晰,这可以用来为命名空间创建可重现的UUID。我想知道是否有一种方法可以验证使用特定名称空间(其UUID)已生成给定的UUID(类型为3或5)吗?
msciwoj

213

类型3和类型5 UUID只是将哈希填充到UUID中的一种技术。

  • 类型1:将MAC地址+日期时间填充到128位中
  • 类型3:将MD5哈希填充到128位中
  • 类型4:将随机数据填充到128位中
  • 类型5:将SHA1哈希填充到128位中
  • 类型6:顺序UUID的非正式概念

SHA1哈希输出160位(20字节);哈希的结果将转换为UUID。

使用SHA1中的20字节哈希:

SHA1 Digest:   74738ff5 5367 e958 9aee 98fffdcd1876 94028007
UUID (v5):     74738ff5-5367-5958-9aee-98fffdcd1876
                             ^_low nibble is set to 5, to indicate type 5
                                  ^_first two bits set to 1 and 0, respectively

(请注意,“ 9”的前两位已经分别是1和0,因此无效)。

我要哈希什么?

您可能想知道我应该算什么。基本上,您哈希以下内容的串联:

sha1([NamespaceUUID]+[AnyString]);

您可以在字符串前面加上所谓的名称空间,以防止名称冲突。

UUID RFC预先定义了你们四个命名空间:

  • NameSpace_DNS:{6ba7b810-9dad-11d1-80b4-00c04fd430c8}
  • NameSpace_URL:{6ba7b811-9dad-11d1-80b4-00c04fd430c8}
  • NameSpace_OID:{6ba7b812-9dad-11d1-80b4-00c04fd430c8}
  • NameSpace_X500:{6ba7b814-9dad-11d1-80b4-00c04fd430c8}

因此,您可以一起哈希:

StackOverflowDnsUUID = sha1(Namespace_DNS + "stackoverflow.com");
StackOverflowUrlUUID = sha1(Namespace_URL + "stackoverflow.com");

然后,RFC定义如何:

  • 从SHA1取160位
  • 并将其转换为128位的UUID

基本要点是仅取前128位,5类型记录中填充a ,然后将该clock_seq_hi_and_reserved节的前两位分别设置为1和0。

更多例子

现在您有了一个生成所谓Name的函数,您可以拥有该函数(以伪代码):

UUID NameToUUID(UUID NamespaceUUID, String Name)
{
    byte[] hash = sha1(NamespaceUUID.ToBytes() + Name.ToBytes());
    UUID result;
    Copy(hash, result, 16);
    result[6] &= 0x0F; 
    result[6] |= 0x50;
    result[8] &= 0x3F; 
    result[8] |= 0x80;
    return result;
}

(请注意,系统的字节顺序会影响上述字节的索引)

您可以拨打电话:

uuid = NameToUUID(Namespace_DNS, 'www.stackoverflow.com');
uuid = NameToUUID(Namespace_DNS, 'www.google.com');
uuid = NameToUUID(Namespace_URL, 'http://www.stackoverflow.com');
uuid = NameToUUID(Namespace_URL, 'http://www.google.com/search&q=rfc+4112');
uuid = NameToUUID(Namespace_URL, 'http://stackoverflow.com/questions/5515880/test-vectors-for-uuid-version-5-converting-hash-into-guid-algorithm');

现在回到您的问题

对于版本3和版本5 UUID,必须指定其他命令行参数名称空间和名称。名称空间可以是字符串表示形式的UUID,也可以是内部预定义名称空间UUID的标识符(当前已知为“ ns:DNS”,“ ns:URL”,“ ns:OID”和“ ns:X500”)。名称是任意长度的字符串。

命名空间是任何UUID你喜欢。它可以是预定义的之一,也可以自己组成,例如:

UUID Namespace_RectalForeignExtractedObject = '8e884ace-bee4-11e4-8dfc-aa07a5b093db'

名称是任意长度的字符串。

名称只是您想要附加到名称空间,然后经过哈希处理并填充到UUID中的文本:

uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'screwdriver');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'toothbrush');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'broomstick');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'orange');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'axe handle');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'impulse body spray');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'iPod Touch');

注意:任何发布到公共领域的代码。无需注明出处。


45
感谢您的详尽解释。如果我可以给我加分的Namespace_RectalForeignExtractedObject话。
boodle

是否可以解码从UUID解码的名称或名称空间?
萨特什

3
@Sathesh不,无法解码哈希值;散列是单向函数。例如,整个《星际迷航》 TNG蓝光收藏品的容量为81 GB,并具有C5740BBBF2429115276D4AB60A020ED3ADE01192的哈希。无法将20字节散列解码回81 GB。如果确实需要,可以尝试对所有可能的GUID和可能的字符串进行哈希处理,直到找到给出相同结果的组合为止。有了它,您会发现它介于永恒和永恒之间。
伊恩·博伊德

21

名称不过是在某些名称空间中唯一的标识符。问题在于名称空间通常很小,一个名称空间经常与其他名称空间冲突。例如,我的汽车的车牌号(名称)在我的州DMV的命名空间中是唯一的,但在世界上可能不是唯一的。其他状态DMV可能在其自己的名称空间中使用了相同的名称。哎呀,其他人的电话号码(名称)也可以匹配,因为那是另一个名称空间,依此类推。

可以将UUID视为驻留在单个名称空间中,以至于它可以为所有内容提供唯一的名称。这就是“通用”的意思。但是,如何将其他命名空间中的现有名称映射到UUID?

一种明显的解决方案是为每个项目生成一个UUID(V1或V4),以替换其不相交的命名空间中的旧名称。缺点是它们更大,您必须将所有新名称传达给拥有数据集副本,更新所有API等的每个人。奇怪的是,您实际上无法完全摆脱旧名称无论如何,这意味着现在每个项目都有两个名称,那么您使事情变好还是变坏了?

这就是V3 / V5出现的地方。UUID 看起来和V4一样随机,但实际上是确定性的。任何拥有正确名称空间UUID的人都可以独立为该名称空间中的任何给定名称生成相同的UUID。您根本不需要发布它们,甚至不需要预先生成它们,因为任何人都可以根据需要即时创建它们!

DNS名称和URL是非常常用的名称空间,因此为它们发布了标准UUID。ASN.1 OID和X.500名称并不常见,但是标准机构喜欢它们,因此它们也为它们发布了标准名称空间UUID。

对于所有其他名称空间,您必须生成自己的名称空间UUID(V1或V4)并将其传达给需要它的任何人。如果您有多个名称空间,则必须为每个名称发布UUID显然不是理想的选择。

这就是层次结构出现的地方:创建一个“基本” UUID(任何类型),然后将其用作命名其他命名空间的命名空间!这样,您只需要发布基本的UUID(或使用明显的UUID),每个人都可以计算出其余的UUID。

例如,让我们继续,我们想为StackOverflow创建一些UUID。在DNS名称空间中具有明显的名称,因此其基数显而易见:

uuid ns_dns = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
uuid ns_base = uuidv5(ns_dns, 'stackoverflow.com');

StackOverflow本身具有用于用户,问题,答案,评论等的独立命名空间,但这些命名空间也很明显:

uuid ns_user = uuidv5(ns_base, 'user');
uuid ns_question = uuidv5(ns_base, 'question');
uuid ns_answer = uuidv5(ns_base, 'answer');
uuid ns_comment = uuidv5(ns_base, 'comment');

这个特定的问题是#10867405,因此它的UUID为:

uuid here = uuidv5(ns_question, '10867405');

请注意,在此过程中没有任何随机性,因此,遵循相同逻辑的任何人都将得到相同的答案,但是UUID名称空间是如此之大,以至于(实际上,鉴于122位加密哈希的安全性)它永远不会与从任何其他名称空间/名称对生成的UUID。


我想知道为什么stackoverflow需要将唯一生成的大整数映射到UUID,因为其API显然仅将大整数作为字符串返回。如果未在API中使用,那么将在哪里使用UUID。看来我们应该选择UUID还是BIGINT?为什么要采用这种混合策略。+1为您的答案提供清晰的解释。
nishant

4
UUID V3 / V5设计用于需要确定性地将现有(且可能冲突的)名称空间转换为一个UUID名称空间的情况,这在合并数据集时通常非常有用。如果那与您的工作不符,请使用V1 / V4。
StephenS
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.