如何创建确定性指南

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

一个显而易见的解决方案是创建一个包含文件名和用于它们的 Guids 的静态字典。然后,每当我们生成文件时,我们查找文件名的字典并使用相应的 guid。但这是不可行的,因为我们可能扩展到100的文件,并不想维护大名单的指南。

因此,另一种方法是根据文件的路径使 Guid 相同。因为我们的文件路径和应用程序目录结构是唯一的,所以 Guid 对于该路径应该是唯一的。因此,每次我们运行升级时,文件都会根据其路径得到相同的 guid。我发现了一个很酷的方法来产生这样的“ 确定性指南”(感谢埃尔顿斯通曼)。它基本上是这样的:

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 将始终是相同的。

Are there any other approaches or recommended ways to doing this? What are the pros or cons of that method?

50874 次浏览

MD5 is weak, I believe you can do the same thing with SHA-1 and get better results.

BTW, just a personal opinion, dressing a md5 hash up as a GUID does not make it a good GUID. GUIDs by their very nature are non Deterministic. this feels like a cheat. Why not just call a spade a spade and just say its a string rendered hash of the input. you could do that by using this line, rather than the new guid line:

string stringHash = BitConverter.ToString(hashBytes)

您需要区分类 Guid的实例和全局唯一的标识符。“确定性 guid”实际上是一个 hash (从对 provider.ComputeHash的调用可以看出)。与通过 Guid.NewGuid创建的 Guid 相比,散列发生冲突(两个不同的字符串产生相同的散列)的几率要高得多。

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

试图将不是纯 GUID 的东西硬塞进 GUID 数据类型可能会导致未来的维护问题..。

正如 Rob 提到的,您的方法不生成 UUID,而是生成一个看起来像 UUID 的散列。

UUID 上的 RFC 4122特别允许确定性(基于名称) UUID-版本3和版本5分别使用 md5和 SHA1。大多数人可能都熟悉版本4,它是随机的。维基百科给出了版本的一个很好的概述。(注意,这里使用‘ version’这个词似乎是在描述 UUID 的‘ type’——版本5并不取代版本4)。

似乎有一些库可以生成3/5版本的 UUID,包括 Python uuid 模块boost.uuid(C + +)和 OSSP UUID。(我还没有找过。净值)

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

中没有本地支持。NET 框架来创建这些,但是我发布了实现算法的 GitHub 上的代码。它可用于以下方面:

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

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

This will convert any string into a Guid without having to import an outside assembly.

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

There are much better ways to generate a unique Guid but this is a way to consistently upgrading a string data key to a Guid data key.

Here's a very simple solution that should be good enough for things like unit/integration tests:

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