【问题标题】:How to Create Deterministic Guids如何创建确定性指南
【发布时间】:2011-02-08 04:28:47
【问题描述】:

在我们的应用程序中,我们正在创建具有 Guid 值的属性的 Xml 文件。此值需要在文件升级之间保持一致。因此,即使文件中的其他所有内容都发生了变化,该属性的 guid 值也应该保持不变。

一个明显的解决方案是创建一个静态字典,其中包含文件名和用于它们的 Guid。然后每当我们生成文件时,我们都会在字典中查找文件名并使用相应的 guid。但这不可行,因为我们可能会扩展到 100 个文件并且不想维护大的 guid 列表。

所以另一种方法是根据文件的路径使 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 总是相同的。

有没有其他方法或推荐的方法来做到这一点?这种方法的优缺点是什么?

【问题讨论】:

    标签: c# .net guid uuid


    【解决方案1】:

    MD5很弱,我相信你可以用SHA-1做同样的事情并获得更好的结果。

    顺便说一句,只是个人意见,将 md5 散列装扮成 GUID 并不能使它成为一个好的 GUID。 GUID 就其本质而言是非确定性的。这感觉像是作弊。为什么不直截了当地说它是一个字符串渲染的输入哈希。你可以通过使用这一行而不是新的 guid 行来做到这一点:

    string stringHash = BitConverter.ToString(hashBytes)
    

    【讨论】:

    • 感谢您的输入,但这仍然给了我一个字符串,我正在寻找一个 GUID...
    • 好的,将您的哈希称为“GUID”,问题已解决。还是您需要 Guid 对象的真正问题?
    • 我希望它是那么简单.. :) 但是是的,我需要一个“GUID”对象
    • “GUID 本质上是非确定性的”——这仅适用于某些类型(“版本”)的 GUID。但是,我同意“将 md5 散列装扮成 GUID 并不能成为一个好的 GUID”,原因是 @Bradley Grainger 和 @Rob Fonseca-Ensor 以及我对这个问题的回答。
    【解决方案2】:

    您需要区分Guid 类的实例和全局唯一的标识符。 “确定性 guid”实际上是一个哈希(正如您对 provider.ComputeHash 的调用所证明的那样)。与通过 Guid.NewGuid 创建的 Guid 相比,散列发生冲突的可能性更高(两个不同的字符串发生相同的散列)。

    因此,您的方法的问题在于,您必须接受两条不同路径会产生相同 GUID 的可能性。如果您需要一个对于任何给定路径字符串都是唯一的标识符,那么最简单的做法是只需使用该字符串。如果您需要对用户隐藏字符串,加密它 - 您可以使用 ROT13 或更强大的东西...

    尝试将不是纯 GUID 的东西硬塞到 GUID 数据类型中可能会导致将来出现维护问题...

    【讨论】:

    • 您声称“与通过 Guid.NewGuid 创建的 Guid 相比,哈希发生冲突的可能性要高得多。”。你能详细说明一下吗?从数学的角度来看,可以设置的位数是相同的,MD5 和 SHA1 都是加密哈希,专门设计用于降低(意外和故意)哈希冲突的概率。
    • 我想说主要区别是加密哈希使用函数从一个无限空间映射到另一个固定空间。将可变长度字符串映射到 128 位的哈希成像,而 Guid 生成伪随机 128 位。伪随机生成不依赖于初始输入,而是通过使用从硬件或其他方式播种的随机性在输出空间中均匀生成输出。
    【解决方案3】:

    正如 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 的)

    【讨论】:

    • 这正是原始海报所追求的。 UUID 已经有一个算法让您从字符串开始并将其转换为 GUID。 UUID 版本 3 使用 MD5 散列字符串,而版本 5 使用 SHA1 散列它。创建“guid”的重点是使其与其他 GUID 相比“独一无二”。该算法定义了必须设置的两个位,以及一个半字节设置为 3 或 5,具体取决于它是版本 3 还是 5。
    • 关于“版本”一词的使用,RFC 4122 §4.1.3 规定:“版本更准确地说是子类型;同样,我们保留了兼容性术语。”
    • 我在 GitHub 上发布了一些用于创建 v3 和 v5 GUID 的 C# 代码:github.com/LogosBible/Logos.Utility/blob/master/src/…
    • @BradleyGrainger,我在符号扩展操作数上使用了按位或运算符警告;考虑先转换为更小的无符号类型
    • 这离题了!建议将单个库错误报告移至 GitHub。
    【解决方案4】:

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

    .NET Framework 中没有用于创建这些的本地支持,但我发布了实现该算法的 code on GitHub。可以这样使用:

    Guid guid = GuidUtility.Create(GuidUtility.UrlNamespace, filePath);
    

    为了进一步降低与其他 GUID 冲突的风险,您可以创建一个私有 GUID 用作命名空间 ID(而不是使用 RFC 中定义的 URL 命名空间 ID)。

    【讨论】:

    • @Porges:RFC4122 不正确,并且有修正 C 代码的勘误表 (rfc-editor.org/errata_search.php?rfc=4122&eid=1352)。如果此实现不完全符合 RFC4122 及其勘误表,请提供更多详细信息;我想让它遵循标准。
    • @BradleyGrainger:我没有注意到,谢谢/抱歉!在阅读 RFC 时,我应该永远记得检查勘误表... :)
    • @Porges:不客气/没问题。令人难以置信的是,他们没有使用勘误表中的更正来就地更新 RFC。即使是文档末尾的链接也会比依靠读者记住搜索勘误表更有帮助(希望编写基于 RFC 的实现之前...)。
    • @BradleyGrainger:如果您使用 HTML 版本,它会从标题中链接到勘误表,例如tools.ietf.org/html/rfc4122。我想知道是否有一个浏览器扩展可以始终重定向到 HTML 版本...
    • 你应该考虑将这个贡献给 .NET .NET repo 在这里:github.com/dotnet/coreclr/tree/master/src/mscorlib/src/System
    【解决方案5】:

    这会将任何字符串转换为 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 数据键的方法。

    【讨论】:

    • 发现这个 sn-p 在使用数据库中的唯一标识符进行联合分发时很有用。
    • 警告!此代码不会生成有效的 Guid / UUID(正如下面提到的 bacar)。版本和类型字段都没有正确设置。
    • 使用 MD5CryptoServiceProvider 代替 SHA1 会不会同样有效,因为 MD5 的长度已经是 16 个字节了?
    【解决方案6】:

    这是一个非常简单的解决方案,应该足以应付单元/集成测试之类的事情:

    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}");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-18
      • 2013-10-30
      • 2013-11-01
      • 1970-01-01
      相关资源
      最近更新 更多