基于字符串生成UUID

4
如何在C#中生成具有字符串命名空间和名称的确定性GUID/UUID v3/v5(根据RFC4122,您需要提供GUID作为命名空间和字符串作为名称)?我想要提供两个字符串而不是GUID供命名空间和名称,并且始终具有相同的GUID/UUID,以便可以进一步将其提供给函数。使用MD5/SHA1哈希命名空间字符串并使用Guid(byte[])构造函数创建新的Guid是否是实现此目标的安全方法,因此我可以将其提供给函数吗? 我不是在问如何通过Guid.TryParse()将类似GUID的字符串解析为命名空间,而是将任何字符串转换为guid命名空间以进一步将其提供给以下函数,但也具有确定性。 根据https://github.com/Faithlife/FaithlifeUtility/blob/master/src/Faithlife.Utility/GuidUtility.cs和RFC 4122,这是应该给定GUID命名空间和字符串名称/任意字符串创建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);
    }

这个QA应该有足够的讨论来回答你所有的疑虑:https://dev59.com/s3E85IYBdhLWcg3wtV_1#9386095 - trailmax
2个回答

0

这个问题的答案最终取决于您与特定命名空间的关系,但让我们先从基础知识开始。

确定性 UUID 必须根据命名空间 UUID 和名称字符串来定义;这是标准所规定的。然而,“命名空间”和“名称”这些术语不一定要映射到您代码中使用的具体命名空间和名称。例如,在 C# 中,类型 System.Guid 可以被认为具有 System 命名空间和 Guid 名称,但实际上将所有 C# 类型标识符的“命名空间”视为 UUID,并使用 System.Guid 作为名称也是可以的(也许更好)。同样地,ISBN 可以使用 urn:isnb: URI 前缀进行标识,但为什么不将所有 URI 的空间视为一个大型命名空间,当该 UUID 已经标准化时呢?

在这方面,命名空间部分可以轻松地被视为格式,即无歧义地定义如何解释(或生成)其后的任何内容。重要的是,由此产生的 UUID 也可以作为自己的命名空间使用,因为它与任何其他 UUID 一样有效。

那么如何决定使用哪个命名空间?有几个选项:

  • 如果您的UUID在一个通常封闭的系统中生成和使用,那么选择一个随机的(v4)UUID作为UUID命名空间,并以某种方式连接您的命名空间和名称来生成UUID名称是没有问题的。您始终可以将命名空间UUID告诉任何想要使用它的人。

  • 如果您希望其他人能够在没有事先通信的情况下找到对象的UUID,则可以选择“众所周知”的命名空间之一,即DNS、URL、OID或X.500,但请注意这(显然)限制了可以在这些命名空间中表示的内容。对于URI的情况,这已经足够丰富,可以标识很多东西,并且(出于链接数据的考虑),您可以使用自己的URI模式,例如http://example.org/users/1作为资源的“真实”标识符(您的命名空间和名称可以转换为该标识符)。

  • 如果您的实体不能直接表示为上述任何一个命名空间,则仍然可以尝试想出一种“合理”的方法来设计层次结构以达到它。理论上,例如,您可以使用类似于http://www.w3.org/2001/XMLSchema#gYear的东西来表示公历日历中所有年份的命名空间,将其转换为c108fbcf-4357-57cd-a8c0-8799e467e87f。可以合理地假设此类命名空间中名称的格式对应于gYear数据类型的词汇空间,因此将其与2021连接(产生abe9231c-2deb-5ae3-a23e-77c2f4657e04)是一种合理的方式来标识当前年份。

    实际上,很少有人关心以这种方式表示实体,但仍然有可能大于0的机会,有些人会考虑这种方法(仅仅因为您现在正在阅读这个答案)。

  • 如果您感到冒险,可以考虑使用nil UUID(00000000-0000-0000-0000-000000000000)作为命名空间,在第一步中将您的命名空间视为UUID名称,在第二步中将其与名称一起使用以获取最终的UUID(可以重复任意长度的元组)。然而,这明显违背了UUID本身的目的,因为从这些元组生成的UUID最合理的事情就是它们本身,此时您可能最好停止使用UUID,只需哈希您的命名空间和名称即可。


0
不,你提出的方法是无效的,因为它根本破坏了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 bar = Guid.Create(nsFoo, "Bar", 5);
这样做的好处是,即使其他人的字符串(除了域名外)与你的完全相同,他们得到的UUID也会完全不同,以防止数据集合并时发生冲突,同时它是完全确定性的、逻辑清晰的和自我说明的。
(注意:我实际上从未使用过C#,所以如果语法有一点错误,请随意修改。我认为模式是清晰的。)

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