如何创建确定性指南

问题描述 投票:0回答:7

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

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

所以另一种方法是根据文件的路径使Guid相同。由于我们的文件路径和应用程序目录结构是唯一的,因此该路径的 Guid 应该是唯一的。因此,每次我们运行升级时,文件都会根据其路径获得相同的 guid。我找到了一种很酷的方法来生成这样的“确定性指南”(感谢 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
7个回答
169
投票

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

.NET Framework 中没有对创建这些内容的本机支持,因此我创建了实现该算法的 NGuid 包。它可以按如下方式使用:

var guid = GuidHelpers.CreateFromName(GuidHelpers.UrlNamespace, filePath);

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


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 数据密钥的方法。


22
投票

正如 Rob 提到的,你的方法不会生成 UUID,它会生成一个看起来像 UUID 的哈希值。

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

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


4
投票

您需要区分类的实例

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

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

尝试将非纯 GUID 的内容硬塞到 GUID 数据类型中可能会导致将来出现维护问题...


2
投票

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

顺便说一句,只是个人意见,将 md5 散列装扮成 GUID 并不能使它成为一个好的 GUID。 GUID 本质上是非确定性的。这感觉就像是一个骗子。为什么不直截了当地说它是输入的字符串呈现的哈希值。您可以使用此行而不是新的引导行来做到这一点:

string stringHash = BitConverter.ToString(hashBytes)

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

0
投票

ARM/bicep 实现了这样的方法: https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-functions-string#guid

虽然效率低且受静态编译保护较少,但可以从 .NET 代码调用 ARM 函数:

using Azure.Deployments.Expression.Expressions;
using Newtonsoft.Json.Linq;

public static string Guid(params string[] values)
{
    var parameters = 
       values.Select(arg => new FunctionArgument(JToken.FromObject(arg))).ToArray();

    return ExpressionBuiltInFunctions.Functions
       .EvaluateFunction("guid", parameters, null).Value<string>()!;
}
© www.soinside.com 2019 - 2024. All rights reserved.