【问题标题】:Generating UUID based on strings基于字符串生成 UUID
【发布时间】:2020-07-30 16:43:23
【问题描述】:

如何在 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 命名空间和字符串名称/任何字符串的情况下创建 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);
    }

【问题讨论】:

标签: c# uuid guid


【解决方案1】:

不,您的建议无效,因为它从根本上破坏了 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#,所以如果我的语法稍有错误,请随时编辑。无论如何,我认为模式很清楚。)

【讨论】:

    【解决方案2】:

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

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

    在这方面,命名空间部分可以很容易地被认为是一种格式,它明确地定义了如何解释(或产生)它之后的任何内容。重要的是,生成的 UUID 也可以单独用作命名空间,因为它与任何其他 UUID 一样有效。

    那么如何决定使用什么命名空间呢?有几种选择:

    • 如果您的 UUID 是在一个通常封闭的系统中生成和使用的,那么为 UUID 命名空间选择一个随机 (v4) 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,而只是散列您的命名空间和名称。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 2011-01-24
      • 2013-01-12
      • 2016-11-30
      相关资源
      最近更新 更多