如何创建确定性 GUIDs

124
在我们的应用程序中,我们创建了一个具有Guid值的属性的Xml文件。此值需要在文件升级之间保持一致。因此,即使文件中的其他所有内容发生更改,属性的guid值也应保持不变。
一个明显的解决方案是创建一个静态字典,其中包含用于文件的文件名和Guids。然后每当我们生成文件时,我们查找文件名的字典并使用相应的唯一标识符。但是,这并不可行,因为我们可能会扩展到数百个文件,并且不想维护大量的唯一标识符列表。
因此,另一种方法是根据文件路径使Guid保持一致。由于我们的文件路径和应用程序目录结构是唯一的,因此该Guid应对该路径唯一。因此,每次运行升级时,文件都基于其路径获取相同的guid。我发现了一种生成此类 'Deterministic Guids'(感谢Elton Stoneman)的酷炫方法。它基本上是这样做的:
private Guid GetDeterministicGuid(string input) 

{ 

//use MD5 hash to get a 16-byte hash of the string: 

MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider(); 

byte[] inputBytes = Encoding.Default.GetBytes(input); 

byte[] hashBytes = provider.ComputeHash(inputBytes); 

//generate a guid from the hash: 

Guid hashGuid = new Guid(hashBytes); 

return hashGuid; 

} 

所以,给定一个字符串,Guid 将始终相同。

还有其他方法或推荐的方式来做这件事吗?那种方法的优缺点是什么?

6个回答

169

正如@bacar所提到的,RFC 4122 §4.3定义了一种创建基于名称的UUID的方法。这样做的优点(而不是仅仅使用MD5哈希)是可以保证它们不会与非命名的UUID发生冲突,并且与其他基于名称的UUID发生冲突的可能性非常小。

.NET Framework没有原生支持创建这些UUID,但我在GitHub上发布了代码来实现该算法。可以按以下方式使用它:

Guid guid = GuidUtility.Create(GuidUtility.UrlNamespace, filePath);
为了进一步减少与其他GUID的冲突风险,您可以创建一个私有GUID作为命名空间ID(而不是使用RFC中定义的URL命名空间ID)。

5
@Porges: RFC4122存在错误,并且有勘误表修复C代码(http://www.rfc-editor.org/errata_search.php?rfc=4122&eid=1352)。 如果此实现未完全符合RFC4122及其勘误表,请提供更多细节; 我想使其遵循标准。 - Bradley Grainger
3
@Porges: 不用谢/没问题。让人难以置信的是他们不会对RFC进行修正更新。即使在文档结尾处提供一个链接,也比依靠读者记得搜索勘误表(希望在基于RFC编写实现之前)更加有帮助。 - Bradley Grainger
2
如果您使用HTML版本,则可以从标题中的链接访问勘误表,例如http://tools.ietf.org/html/rfc4122。我想知道是否有浏览器扩展程序始终将其重定向到HTML版本... - porges
5
你应该考虑向.NET贡献这个项目。.NET代码库在这里:https://github.com/dotnet/coreclr/tree/master/src/mscorlib/src/System - sapphiremirage
2
Github对我来说非常完美,谢谢。这个要点是我所做的修改副本,以便剥离所有与命名空间GUID无关的不必要部分。https://gist.github.com/angularsen/92a3ba9d9a94d250accd257f9f5a3d54 - angularsen
显示剩余2条评论

35

这将把任何字符串转换为Guid,无需导入外部程序集。

public static Guid ToGuid(string src)
{
    byte[] stringbytes = Encoding.UTF8.GetBytes(src);
    byte[] hashedBytes = new System.Security.Cryptography
        .SHA1CryptoServiceProvider()
        .ComputeHash(stringbytes);
    Array.Resize(ref hashedBytes, 16);
    return new Guid(hashedBytes);
}

虽然有更好的方法来生成唯一的 Guid,但这种方法可以将字符串数据键始终升级为 Guid 数据键。


发现这个片段在使用数据库进行联合分布时使用唯一标识符非常有用。 - Gleno
13
警告!这段代码无法生成有效的 GUID / UUID(就像下面 bacar 也提到的那样)。版本和类型字段都没有正确设置。 - MarkusSchaber
3
使用MD5CryptoServiceProvider而不是SHA1是否同样有效,因为MD5已经是16字节长度了? - Brain2000

22

正如Rob所提到的,你的方法并没有生成UUID,而是生成了一个类似于UUID的哈希值。

关于UUID的RFC 4122特别允许确定性(基于名称)UUID - 版本3和5分别使用md5和SHA1。大多数人可能熟悉版本4,它是随机的。Wikipedia提供了版本概述。 (请注意,此处使用的“版本”一词似乎描述了UUID的“类型”-版本5并不超过版本4)。

似乎有一些库可用于生成版本3/5的UUID,包括python uuid moduleboost.uuid(C++)和OSSP UUID。(我没有为任何.net查找过)


1
这正是原帖作者想要的。UUID已经为您提供了一个算法,可以从字符串开始并将其转换为GUID。 UUID版本3使用MD5对字符串进行哈希处理,而版本5使用SHA1进行哈希处理。创建“guid”的重要点是使其与其他GUIDS“唯一”。该算法定义了必须设置的两个位以及一个nibble,根据版本3或5设置为3或5。 - Ian Boyd
2
关于使用“版本”一词,RFC 4122 §4.1.3指出:“版本更准确地说是一个子类型;我们保留这个术语以保持兼容性。” - Bradley Grainger
13
我在 GitHub 上发布了一些 C# 代码,用于创建v3和v5 GUID:https://github.com/LogosBible/Logos.Utility/blob/master/src/Logos.Utility/GuidUtility.cs - Bradley Grainger
@BradleyGrainger,我收到了警告:位或运算符用于符号扩展的操作数;请先考虑将其转换为较小的无符号类型。 - Sebastian
1
这已经偏离主题了!建议将单独的库错误报告移动到GitHub。 - bacar
显示剩余2条评论

4
你需要区分类Guid的实例和全局唯一标识符之间的区别。一个“确定性guid”实际上是一个哈希(正如你调用provider.ComputeHash所证明的那样)。与通过Guid.NewGuid创建的Guid相比,哈希碰撞(两个不同的字符串恰好产生相同的哈希)的可能性更高。
因此,你的方法存在问题,即你必须接受两个不同路径可能会生成相同GUID的可能性。如果你需要一个对于任何给定路径字符串都唯一的标识符,那么最简单的方法就是只使用字符串。如果你需要隐藏这个字符串,可以加密它 - 你可以使用ROT13或者更强大的算法...
试图将不是纯GUID的东西塞入GUID数据类型中可能会导致未来的维护问题...

2
你声称“哈希碰撞的概率比通过Guid.NewGuid创建的Guid高得多”。你能详细说明一下吗?从数学角度来看,可以设置的位数是相同的,而MD5和SHA1都是密码哈希函数,专门设计用于降低(意外和故意的)哈希碰撞的概率。 - MarkusSchaber
我认为主要区别在于,加密哈希使用函数将一个无限空间映射到另一个固定空间。想象一下,哈希将可变长度的字符串映射到128位,而Guid生成伪随机的128位。伪随机生成不依赖于初始输入,而是通过使用从硬件或其他手段种子化的随机性在输出空间中均匀地生成输出。 - Thai Bui

2

MD5是弱密码,我相信你可以使用SHA-1完成同样的任务并获得更好的结果。

顺便说一下,这只是个人意见,将MD5哈希值伪装成GUID并不能使它成为一个好的GUID。GUID本质上是不确定性的。这感觉像是作弊。为什么不直接称其为输入的字符串渲染哈希值呢?你可以使用以下代码行来实现这一点,而不是使用新的guid行:

string stringHash = BitConverter.ToString(hashBytes)

谢谢您的回复,但这仍然给了我一个字符串,而我正在寻找一个GUID... - Punit Vora
好的,把你的哈希叫做“GUID”,问题解决了。或者真正的问题是你需要一个Guid对象? - user7116
我希望它是那么简单... :) 但是是的,我需要一个“GUID”对象。 - Punit Vora
6
“GUID(全局唯一标识符)的本质是非确定性的” - 这只适用于某些类型(“版本”)的GUID。不过,我同意@Bradley Grainger和@Rob Fonseca-Ensor所阐述的其他原因,认为“将md5哈希值伪装成GUID并不是一个好的GUID”,我的回答就是这个问题。 - bacar

2

这里有一个非常简单的解决方案,适用于像单元/集成测试之类的东西:

var rnd = new Random(1234); // Seeded random number (deterministic).
Console.WriteLine($"{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}-{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}-{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}-{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}-{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}{rnd.Next(0, 255):x2}");

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