【问题标题】:Is this a cryptographically strong Guid?这是一个加密强的 Guid 吗?
【发布时间】:2025-12-08 09:20:03
【问题描述】:

我正在考虑使用 Guid 作为网站的随机匿名访问者标识符(存储为 cookie 客户端大小和数据库服务器端),并且我想要一种生成 Guid 的加密方式强(以尽量减少碰撞的机会)。

作为记录,Guid 中有 16 个字节(或 128 位)。

这就是我的想法:

/// <summary>
/// Generate a cryptographically strong Guid
/// </summary>
/// <returns>a random Guid</returns>
private Guid GenerateNewGuid()
{
    byte[] guidBytes = new byte[16]; // Guids are 16 bytes long
    RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
    random.GetBytes(guidBytes);
    return new Guid(guidBytes);
}

有没有更好的方法来做到这一点?

编辑: 这将用于两个目的,访问者的唯一 ID 和购买的交易 ID(这将简要地成为查看/更新敏感信息所需的令牌)。

【问题讨论】:

  • 如果您想尽量减少使用普通 guid 发生冲突的机会,它已经具备您需要的所有保护措施。现在具有不可预测的随机 id,这是具有不同解决方案的不同要求。那么您的真正要求是什么,没有冲突或不可预测性(在同一键空间上发生冲突的可能性更高)?

标签: c# random guid


【解决方案1】:

在回答 OP 的实际问题时,这是否具有加密强度,答案是肯定的,因为它是直接从 RNGCryptoServiceProvider 创建的。但是,根据此 SO 答案,当前接受的答案提供了一个绝对不是加密安全的解决方案:

Is Microsoft's GUID generator cryptographically secure.

由于理论上缺乏唯一性(通过数据库查找很容易检查),这在架构上是否是正确的方法是另一个问题。

【讨论】:

    【解决方案2】:

    所以,从技术上讲,您正在构建的不是 GUID。 GUID 是全局唯一标识符。您正在构建一个 128 位的随机字符串。我建议像之前的回答者一样,使用内置的 GUID 生成方法。此方法有(尽管非常小)生成重复 GUID 的机会。

    使用内置功能有一些优势,包括跨机器唯一性 [部分原因是由于在 guid 中引用了 MAC 地址,请参见此处:http://en.wikipedia.org/wiki/Globally_Unique_Identifier

    无论您是否使用内置方法,我都建议您不要向客户公开购买 GUID。 Microsoft 代码使用的标准方法是公开一个会话 GUID,它可以识别客户并相对较快地过期。 Cookie 跟踪客户用户名和保存的密码以创建会话。因此,您的“短期购买 ID”实际上永远不会传递给客户(或者更重要的是,从客户那里接收),并且在您的客户的个人信息和整个互联网之间有一堵更持久的墙。

    【讨论】:

    • 我不想深入了解架构细节,但我需要在两个网站之间维护一个会话(一个用于购物和地址管理,一个用于支付处理——一个包含两个不同的子域)领域)。当前的设计是使用链接(或重定向)让客户在购买时根据需要从一个链接到另一个链接。
    【解决方案3】:

    碰撞在理论上是不可能的(它不是全球唯一的),但可预测性是另一个问题。正如 Christopher Stevenson 正确指出的那样,给定一些先前生成的 GUID,实际上可以在比您想象的要小得多的键空间内开始预测模式。 GUID 保证唯一性,而不是可预测性。大多数算法都会考虑到它,但你永远不应该指望它,尤其是不要将其作为购买的交易 ID,无论多么简单。您正在为暴力会话劫持攻击打开大门。

    要创建一个正确的唯一 ID,请从您的系统中获取一些随机内容,附加一些访问者特定信息,并在服务器上附加一个只有您知道的字符串,然后在整个内容上使用一个好的哈希算法。与 GUID 不同,哈希意味着不可预测且不可逆转。

    为了简化:如果您只关心唯一性,为什么不给所有访问者提供从 1 到无穷大的连续整数。保证是独一无二的,非常可预测的是,当您刚购买了 684 项目时,您可以在 685 开始破解,直到它出现。

    【讨论】:

      【解决方案4】:

      为了避免碰撞:

      1. 如果您无法保持全局计数,请使用Guid.NewGuid()
      2. 否则,增加一些整数并使用 1、2、3、4...

      “但是这不是很容易猜到吗?”

      是的,但意外和故意碰撞是不同的问题,有不同的解决方案,最好分开解决,请注意最少,因为可预测性有助于防止意外碰撞,同时使故意碰撞更容易。

      如果你可以全局递增,那么数字 2 保证没有冲突。 UUID 的发明是为了在没有全局跟踪能力的情况下进行近似。

      假设我们使用递增整数。假设我们在给定案例中的 ID 是 123。

      然后我们可以这样做:

      private static string GetProtectedID(int id)
      {
        using(var sha = System.Security.Cryptography.SHA1.Create())
        {
          return string.Join("", sha.ComputeHash(Encoding.UTF8.GetBytes(hashString)).Select(b => b.ToString("X2"))) + id.ToString();
        }
      }
      

      产生09C495910319E4BED2A64EA16149521C51791D8E123。要将其解码回我们所做的 id:

      private static int GetIDFromProtectedID(string str)
      {
        int chkID;
        if(int.TryParse(str.Substring(40), out chkID))
        {
          string chkHash = chkID.ToString() + "this is my secret seed kjٵتשڪᴻᴌḶḇᶄ™∞ﮟﻑfasdfj90213";
          using(var sha = System.Security.Cryptography.SHA1.Create())
          {
            if(string.Join("", sha.ComputeHash(Encoding.UTF8.GetBytes(hashString)).Select(b => b.ToString("X2"))) == str.Substring(0, 40))
              return chkID;
          }
        }
        return 0;//or perhaps raise an exception here.
      }
      

      即使有人猜到他们得到了数字 123,也不会让他们推断出 122 的 id 是B96594E536C9F10ED964EEB4E3D407F183FDA043122

      或者,这两者可以作为单独的标记给出,依此类推。

      【讨论】:

        【解决方案5】:

        【讨论】:

        • 不是 NewGuid() somewhat predictable?
        • 我无法真正谈论算法,但我的印象是,由于可能的 guid 数量众多,很难猜测您的系统生成的 guid。冲突极为罕见,如果您担心的话,当然可以在数据库中附加一个约束。似乎 OP 主要关注唯一性,因此可预测性甚至可能是次要问题。
        • 为简单起见假设统一概率,如果地球上每个人都拥有 6 亿个 GUID,那么重复的概率约为 50%。
        • @dspiegs 冲突不是当前的问题,而是在其他人进行购买时暴力破解会话 GUID 的可能性。
        • @Servy 1) 有些人仍然这样做,而且您不知道他们将如何在 .NET 5.0 中对其进行更改,这并不重要,因为其他算法仍会采取措施保持独特性在数十亿台计算机中,严重限制了密钥空间并使其可预测,2)这就是整个问题,它们都是在同一台机器上生成的,因此容易受到模式的影响。