根据字符串生成UUID
Generating UUID based on strings
如何在 C# 中生成具有命名空间和名称作为字符串的确定性 GUID/UUIDs v3/v5(根据 RFC4122,您需要提供作为 GUID 的命名空间和作为字符串的名称)提供给函数,所以我想提供两个字符串,而不是命名空间的 guid 和名称的字符串,并且命名空间的字符串和名称的字符串始终具有相同的 GUID/UUID。
是否使用 MD5/SHA1 散列 namespace 字符串并通过 Guid(byte[]) 构造函数创建新的 Guid 是一种安全的方法来完成此操作,因此我可以进一步提供它来运行?
我不是在询问通过 Guid.TryParse() 将类似 guid 的字符串解析到名称空间,而是将任何字符串转换为 guid 名称空间以进一步为下面的函数提供它,但它也是确定性的。
根据 https://github.com/Faithlife/FaithlifeUtility/blob/master/src/Faithlife.Utility/GuidUtility.cs
和 RFC 4122
这就是给定 GUID 命名空间和字符串 name/any string.
创建 GUID 的方式
/// <summary>
/// Creates a name-based UUID using the algorithm from RFC 4122 §4.3.
/// </summary>
/// <param name="namespaceId">The ID of the namespace.</param>
/// <param name="nameBytes">The name (within that namespace).</param>
/// <param name="version">The version number of the UUID to create; this value must be either
/// 3 (for MD5 hashing) or 5 (for SHA-1 hashing).</param>
/// <returns>A UUID derived from the namespace and name.</returns>
public static Guid Create(Guid namespaceId, byte[] nameBytes, int version)
{
if (version != 3 && version != 5)
throw new ArgumentOutOfRangeException(nameof(version), "version must be either 3 or 5.");
// convert the namespace UUID to network order (step 3)
byte[] namespaceBytes = namespaceId.ToByteArray();
SwapByteOrder(namespaceBytes);
// compute the hash of the namespace ID concatenated with the name (step 4)
byte[] data = namespaceBytes.Concat(nameBytes).ToArray();
byte[] hash;
using (var algorithm = version == 3 ? (HashAlgorithm) MD5.Create() : SHA1.Create())
hash = algorithm.ComputeHash(data);
// most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12)
byte[] newGuid = new byte[16];
Array.Copy(hash, 0, newGuid, 0, 16);
// set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the appropriate 4-bit version number from Section 4.1.3 (step 8)
newGuid[6] = (byte) ((newGuid[6] & 0x0F) | (version << 4));
// set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively (step 10)
newGuid[8] = (byte) ((newGuid[8] & 0x3F) | 0x80);
// convert the resulting UUID to local byte order (step 13)
SwapByteOrder(newGuid);
return new Guid(newGuid);
}
不,您提出的建议无效,因为它从根本上破坏了 UUID 的工作方式。为您的命名空间使用真实的 UUID。
实现此目的的一种方便(且有效)的方法是分层命名空间。首先,使用标准的 DNS 命名空间 UUID 加上您的域名来生成您的根命名空间:
Guid nsDNS = new Guid("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
Guid nsRoot = Guid.Create(nsDNS, "myapp.example.com", 5);
然后为您的字符串创建命名空间 UUID:
Guid nsFoo = Guid.Create(nsRoot, "Foo", 5);
现在您可以使用带有个人名称的新 Foo 命名空间 UUID:
引导栏 = Guid.Create(nsFoo, "Bar", 5);
这样做的好处是其他任何人都将获得与您完全不同的 UUID,即使他们的字符串(显然除了域)与您的字符串相同,如果您的数据集被合并,则可以防止冲突,但它是完全确定性、逻辑性和自我记录。
(注意:我从未真正使用过 C#,所以如果我的语法稍有错误,请随意编辑。无论如何我认为模式很清楚。)
这个问题的答案最终取决于你与特定名字的关系space,但让我们先从基础开始。
确定性 UUID 必须 根据名称 space UUID 和名称字符串定义;这是标准规定的。但是,术语“namespace”和“name”不一定必须映射到代码中使用的具体 namespace 和名称。例如,C# 中的类型 System.Guid 可以被认为具有 System
名称 space 和 Guid
名称,但是实际上将所有 C# 类型标识符的“名称 space”标识为 UUID 并使用 System.Guid
作为名称也很好(也许更好)。类似地,ISBN 可以使用 urn:isnb:
URI 前缀来识别,但为什么不将所有 URI 的 space 视为一个大名称 space,因为它的 UUID 已经标准化?
在这方面,名称space 部分很容易被认为是一种格式,明确定义了如何解释(或生成)其后的任何内容。重要的是,生成的 UUID 也可以单独用作名称space,因为它与任何其他 UUID 一样有效。
那么如何决定使用什么名字space呢?有几种选择:
如果您的 UUID 是在一个通常封闭的系统中生成和使用的,那么为 UUID 名称选择一个随机 (v4) UUID 没有错space 并且只是连接您的名称space 并以某种方式命名以生成 UUID 名称。您可以随时将名称space UUID 告诉任何想使用它的人。
如果您希望其他人能够在没有事先沟通的情况下找到您的对象的 UUID,您可以选择一个“知名”名称space,即 DNS 、URL、OID 或 X.500,但请注意,这(显然)将可以识别的内容限制为可以在这些名称 space 中表示的内容。对于 URI 的情况,这已经足够丰富,可以识别很多东西,并且(出于链接数据的考虑)您可以使用自己的 URI 模式,例如 http://example.org/users/1
作为资源的“真实”标识符(你的名字space和名字可以转换成那个)。
如果您的实体不能直接用上述名称之一表示space,您仍然可以尝试想出一种“合理”的方式来设计层次结构,以便达到它。理论上,举个例子,你可以用http://www.w3.org/2001/XMLSchema#gYear
这样的东西来表示公历所有年份的名称space,把它变成c108fbcf-4357-57cd-a8c0-8799e467e87f
。可以合理地假设这样的名称 space 中的名称格式对应于 gYear
数据类型的词法 space,因此将其与 2021
连接(产生 abe9231c-2deb-5ae3-a23e-77c2f4657e04
) 将是识别当前年份的合理方式。
在实践中,没有多少人关心能够以这种方式表示实体,但仍然有大于 0 的机会有人会考虑这种方法(仅仅因为你阅读了这个答案现在)。
如果您喜欢冒险,您可以考虑使用 nil UUID (00000000-0000-0000-0000-000000000000
) 作为名称space,使用两步法首先处理您的namespace 作为第一步中的 UUID 名称,然后在第二步中将其与您的姓名一起使用以获得最终的 UUID(对于任何长度的元组都可以重复)。然而,这公然违背了 UUID 本身的目的,因为从这些元组生成的 UUID 可以代表的最合理的东西是元组本身,此时你最好停止使用 UUID,而只是散列你的名字 space 和名字.
如何在 C# 中生成具有命名空间和名称作为字符串的确定性 GUID/UUIDs v3/v5(根据 RFC4122,您需要提供作为 GUID 的命名空间和作为字符串的名称)提供给函数,所以我想提供两个字符串,而不是命名空间的 guid 和名称的字符串,并且命名空间的字符串和名称的字符串始终具有相同的 GUID/UUID。 是否使用 MD5/SHA1 散列 namespace 字符串并通过 Guid(byte[]) 构造函数创建新的 Guid 是一种安全的方法来完成此操作,因此我可以进一步提供它来运行? 我不是在询问通过 Guid.TryParse() 将类似 guid 的字符串解析到名称空间,而是将任何字符串转换为 guid 名称空间以进一步为下面的函数提供它,但它也是确定性的。 根据 https://github.com/Faithlife/FaithlifeUtility/blob/master/src/Faithlife.Utility/GuidUtility.cs 和 RFC 4122 这就是给定 GUID 命名空间和字符串 name/any string.
创建 GUID 的方式 /// <summary>
/// Creates a name-based UUID using the algorithm from RFC 4122 §4.3.
/// </summary>
/// <param name="namespaceId">The ID of the namespace.</param>
/// <param name="nameBytes">The name (within that namespace).</param>
/// <param name="version">The version number of the UUID to create; this value must be either
/// 3 (for MD5 hashing) or 5 (for SHA-1 hashing).</param>
/// <returns>A UUID derived from the namespace and name.</returns>
public static Guid Create(Guid namespaceId, byte[] nameBytes, int version)
{
if (version != 3 && version != 5)
throw new ArgumentOutOfRangeException(nameof(version), "version must be either 3 or 5.");
// convert the namespace UUID to network order (step 3)
byte[] namespaceBytes = namespaceId.ToByteArray();
SwapByteOrder(namespaceBytes);
// compute the hash of the namespace ID concatenated with the name (step 4)
byte[] data = namespaceBytes.Concat(nameBytes).ToArray();
byte[] hash;
using (var algorithm = version == 3 ? (HashAlgorithm) MD5.Create() : SHA1.Create())
hash = algorithm.ComputeHash(data);
// most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12)
byte[] newGuid = new byte[16];
Array.Copy(hash, 0, newGuid, 0, 16);
// set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the appropriate 4-bit version number from Section 4.1.3 (step 8)
newGuid[6] = (byte) ((newGuid[6] & 0x0F) | (version << 4));
// set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively (step 10)
newGuid[8] = (byte) ((newGuid[8] & 0x3F) | 0x80);
// convert the resulting UUID to local byte order (step 13)
SwapByteOrder(newGuid);
return new Guid(newGuid);
}
不,您提出的建议无效,因为它从根本上破坏了 UUID 的工作方式。为您的命名空间使用真实的 UUID。
实现此目的的一种方便(且有效)的方法是分层命名空间。首先,使用标准的 DNS 命名空间 UUID 加上您的域名来生成您的根命名空间:
Guid nsDNS = new Guid("6ba7b810-9dad-11d1-80b4-00c04fd430c8"); Guid nsRoot = Guid.Create(nsDNS, "myapp.example.com", 5);
然后为您的字符串创建命名空间 UUID:
Guid nsFoo = Guid.Create(nsRoot, "Foo", 5);
现在您可以使用带有个人名称的新 Foo 命名空间 UUID:
引导栏 = Guid.Create(nsFoo, "Bar", 5);
这样做的好处是其他任何人都将获得与您完全不同的 UUID,即使他们的字符串(显然除了域)与您的字符串相同,如果您的数据集被合并,则可以防止冲突,但它是完全确定性、逻辑性和自我记录。
(注意:我从未真正使用过 C#,所以如果我的语法稍有错误,请随意编辑。无论如何我认为模式很清楚。)
这个问题的答案最终取决于你与特定名字的关系space,但让我们先从基础开始。
确定性 UUID 必须 根据名称 space UUID 和名称字符串定义;这是标准规定的。但是,术语“namespace”和“name”不一定必须映射到代码中使用的具体 namespace 和名称。例如,C# 中的类型 System.Guid 可以被认为具有 System
名称 space 和 Guid
名称,但是实际上将所有 C# 类型标识符的“名称 space”标识为 UUID 并使用 System.Guid
作为名称也很好(也许更好)。类似地,ISBN 可以使用 urn:isnb:
URI 前缀来识别,但为什么不将所有 URI 的 space 视为一个大名称 space,因为它的 UUID 已经标准化?
在这方面,名称space 部分很容易被认为是一种格式,明确定义了如何解释(或生成)其后的任何内容。重要的是,生成的 UUID 也可以单独用作名称space,因为它与任何其他 UUID 一样有效。
那么如何决定使用什么名字space呢?有几种选择:
如果您的 UUID 是在一个通常封闭的系统中生成和使用的,那么为 UUID 名称选择一个随机 (v4) UUID 没有错space 并且只是连接您的名称space 并以某种方式命名以生成 UUID 名称。您可以随时将名称space UUID 告诉任何想使用它的人。
如果您希望其他人能够在没有事先沟通的情况下找到您的对象的 UUID,您可以选择一个“知名”名称space,即 DNS 、URL、OID 或 X.500,但请注意,这(显然)将可以识别的内容限制为可以在这些名称 space 中表示的内容。对于 URI 的情况,这已经足够丰富,可以识别很多东西,并且(出于链接数据的考虑)您可以使用自己的 URI 模式,例如
http://example.org/users/1
作为资源的“真实”标识符(你的名字space和名字可以转换成那个)。如果您的实体不能直接用上述名称之一表示space,您仍然可以尝试想出一种“合理”的方式来设计层次结构,以便达到它。理论上,举个例子,你可以用
http://www.w3.org/2001/XMLSchema#gYear
这样的东西来表示公历所有年份的名称space,把它变成c108fbcf-4357-57cd-a8c0-8799e467e87f
。可以合理地假设这样的名称 space 中的名称格式对应于gYear
数据类型的词法 space,因此将其与2021
连接(产生abe9231c-2deb-5ae3-a23e-77c2f4657e04
) 将是识别当前年份的合理方式。在实践中,没有多少人关心能够以这种方式表示实体,但仍然有大于 0 的机会有人会考虑这种方法(仅仅因为你阅读了这个答案现在)。
如果您喜欢冒险,您可以考虑使用 nil UUID (
00000000-0000-0000-0000-000000000000
) 作为名称space,使用两步法首先处理您的namespace 作为第一步中的 UUID 名称,然后在第二步中将其与您的姓名一起使用以获得最终的 UUID(对于任何长度的元组都可以重复)。然而,这公然违背了 UUID 本身的目的,因为从这些元组生成的 UUID 可以代表的最合理的东西是元组本身,此时你最好停止使用 UUID,而只是散列你的名字 space 和名字.