【问题标题】:How to generate a LONG guid?如何生成长指南?
【发布时间】:2010-05-19 17:07:26
【问题描述】:

我想生成一个长 UUID - 类似于 gmail 使用的会话密钥。它应至少为 256 个字符且不超过 512 个字符。它可以包含所有字母数字字符和一些特殊字符(键盘上功能键下方的字符)。这已经完成了还是有样品?

C++ 或 C#

更新:GUID 是不够的。我们已经看到了冲突,需要对此进行补救。 512 是目前的最大值,因为它会阻止我们更改已经发货的东西。

更新 2:对于那些坚持 GUID 有多独特的人,如果有人想猜测您的下一个会话 ID,他们不必计算未来 1 万亿年的组合。他们所要做的就是使用约束时间因素,他们将在几个小时内完成。

【问题讨论】:

  • 全球唯一还不够唯一吗?
  • @John:什么,你的应用不需要支持星际帝国?
  • 不管他问的是否不必要,为一个诚实的问题投反对票似乎毫无意义......
  • 你为什么不直接回答他的问题,不要带着所有的讽刺和坏情绪?
  • @bitschnau 我不同意。提问者可能不知道 Guid 是独一无二的。如果只是他真的不需要LONG guid的话。如果他只需要一些字节序列,那么他需要的不是指导

标签: c# c++ algorithm


【解决方案1】:

如果您的 GUID 发生冲突,请问您是如何生成它们的?

GUID 发生冲突的可能性在天文上是不可能的,因为它们基于:

  • 60 位 - 生成期间的时间戳
  • 48 位 - 计算机标识符
  • 14 位 - 唯一 ID
  • 6 位是固定的

您必须在同一时刻在同一台机器上运行 GUID 生成大约 50 次,才能有 50% 的碰撞几率。请注意,瞬间可以精确到纳秒。

更新:

根据您的评论“将 GUID 放入哈希表”...GetHashCode() 方法是导致冲突的原因,而不是 GUID:

public override int GetHashCode()
{
    return ((this._a ^ ((this._b << 0x10) | ((ushort) this._c))) ^ ((this._f << 0x18) | this._k));
}

你可以看到它返回了一个int,所以如果你在哈希表中有超过 2^32 个“GUID”,你就会 100% 发生冲突。

【讨论】:

  • 尝试在 sql-server 2005 中插入。那是我们看到的冲突
  • @Ron:你总是可以让 SQL 给你 GUID。
  • 这是一个很好的观点。 OP 应该修改他的测试程序以在发生碰撞时检查是否相等。这样您就可以区分 2 个具有冲突哈希码的 GUID 和 2 个相同的 GUID。
【解决方案2】:

根据您的更新2,您对 Guid 的看法是正确的,即使 msdn 引用它也是可以预测的。这是一种使用加密强随机数生成器来创建 ID 的方法。

static long counter; //store and load the counter from persistent storage every time the program loads or closes.

public static string CreateRandomString(int length)
{
    long count = System.Threading.Interlocked.Increment(ref counter);
    int PasswordLength = length;
    String _allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789";
    Byte[] randomBytes = new Byte[PasswordLength];
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    rng.GetBytes(randomBytes);
    char[] chars = new char[PasswordLength];
    int allowedCharCount = _allowedChars.Length;
    for (int i = 0; i < PasswordLength; i++)
    {
        while(randomBytes[i] > byte.MaxValue - (byte.MaxValue % allowedCharCount))
        {
            byte[] tmp = new byte[1];
            rng.GetBytes(tmp);
            randomBytes[i] = tmp[0];
        }
        chars[i] = _allowedChars[(int)randomBytes[i] % allowedCharCount];
    }
    byte[] buf = new byte[8];
    buf[0] = (byte) count;
    buf[1] = (byte) (count >> 8);
    buf[2] = (byte) (count >> 16);
    buf[3] = (byte) (count >> 24);
    buf[4] = (byte) (count >> 32);
    buf[5] = (byte) (count >> 40);
    buf[6] = (byte) (count >> 48);
    buf[7] = (byte) (count >> 56);
    return Convert.ToBase64String(buf) + new string(chars);
}

编辑我知道有一些偏差,因为allowedCharCount 不能被 255 整除,如果它落在无人区,你可以摆脱偏差并获得一个新的随机数剩下的。

EDIT2 - 这不保证是唯一的,你可以持有一个静态的 64 位(或更高,如果需要)单调计数器将其编码为 base46,并将其作为 id 的前 4-5 个字符。

更新 - 现在保证是唯一的

更新 2:算法现在变慢但消除了偏差。

编辑:我刚刚进行了一个测试,我想让你知道 ToBase64String 可以返回非字母数字字符(如 1 编码到"AQAAAAAAAAA="),所以你知道。

新版本:

取自此页面上的Matt Dotson's answer,如果您不那么担心密钥空间,您可以这样做,它会运行得更快。

public static string CreateRandomString(int length)
{
    length -= 12; //12 digits are the counter
    if (length <= 0)
        throw new ArgumentOutOfRangeException("length");
    long count = System.Threading.Interlocked.Increment(ref counter);
    Byte[] randomBytes = new Byte[length * 3 / 4];
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    rng.GetBytes(randomBytes);

    byte[] buf = new byte[8];
    buf[0] = (byte)count;
    buf[1] = (byte)(count >> 8);
    buf[2] = (byte)(count >> 16);
    buf[3] = (byte)(count >> 24);
    buf[4] = (byte)(count >> 32);
    buf[5] = (byte)(count >> 40);
    buf[6] = (byte)(count >> 48);
    buf[7] = (byte)(count >> 56);
    return Convert.ToBase64String(buf) + Convert.ToBase64String(randomBytes);
}

【讨论】:

  • 从技术上讲,您仍然不能保证是独一无二的。当您用完计数器位并开始循环时,您将极有可能发生碰撞。如果您要保留一个计数器,您可以使用 System.Numerics.BigInteger 并永远计数。
  • 如果他每年 356 天、每天 24 小时每秒生成 11,574 个新 id(每天 10 亿个),那么它需要 5050 万年才能完成。我认为 64 位会很好。
  • 使用您生成随机字符串的方法是否可以安全地生成唯一的产品密钥?
  • @DavidAnderson 是的,但是如果要在客户端上验证产品密钥而不连接到服务器来检查它是否有效,它将无法工作。对于这种方法,您需要某种方法来验证密钥是否“正确”。请参阅 this SO Question 以获取有关这样做的说明。
  • 我已经编写好了激活服务器并准备好了,我只是想确保该算法足以生成密钥(我测试了它生成 1,000,000 个唯一密钥并且没有冲突),所以看起来像一个好方法。 :) 我在 Base32Encoding 之类的问题上苦苦挣扎。
【解决方案3】:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < HOW_MUCH_YOU_WANT / 32; i++)
   sb.Append(Guid.NewGuid().ToString("N"));
return sb.ToString();

但是为了什么?

【讨论】:

    【解决方案4】:

    这里的问题是为什么,而不是如何。比 GUID更大的会话 ID 是没有用的,因为它已经大到足以阻止暴力攻击。

    如果您担心预测 GUID,请不要担心。与早期的顺序 GUID 不同,V4 GUID 是基于 RC4 的加密安全。我所知道的唯一漏洞利用依赖于对生成值的进程的内部状态的完全访问权限,因此如果您拥有的只是 GUID 的部分序列,它就无法将您带到任何地方。

    如果您偏执,请生成一个 GUID,使用 SHA-1 之类的东西对其进行哈希处理,然后使用该值。然而,这是浪费时间。如果您担心会话劫持,您应该关注 SSL,而不是这个。

    【讨论】:

      【解决方案5】:
      byte[] random = new Byte[384];
      
      //RNGCryptoServiceProvider is an implementation of a random number generator.
      RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
      rng.GetBytes(random);
      var sessionId = Convert.ToBase64String(random);
      

      您可以将 base64 编码中的“/”和“=”替换为您可以接受的任何特殊字符。

      Base64 编码创建一个比字节数组大 4/3 的字符串(因此 384 个字节应该给你 512 个字符)。

      这应该比 base16(十六进制)编码的 guid 为您提供更多数量级的值。 512^16 与 512^64

      此外,如果您将这些放在 sql server 中,请确保关闭不区分大小写。

      【讨论】:

        【解决方案6】:

        有两种非常简单的方法(C#):

        1) 使用 Guid.NewGuid().ToString("N") 生成一堆 Guid。每个 GUID 的长度为 32 个字符,因此只需生成其中的 8 个并将它们连接起来即可得到 256 个字符。

        2) 创建一个常量字符串 (const string sChars = "abcdef"),其中包含您希望在 UID 中使用的可接受字符。然后在一个循环中,通过随机生成一个从 0 到可接受字符字符串(sChars)长度的数字,从该字符串中随机选择字符,并将它们连接到一个新字符串中(使用 stringbuilder 使其性能更高,但字符串会也工作)。

        【讨论】:

        • 后一种技术不能保证唯一,因为它是随机的。不太可能发生冲突,但不能保证。
        • 当然,第一种技术也不能保证唯一,并且比第二种更容易发生冲突。 :)
        【解决方案7】:

        您可能想查看 boost 的 Uuid Library。它支持多种生成器,包括可能满足您需求的随机生成器。

        【讨论】:

          【解决方案8】:

          我会使用某种 std::time() 的散列,可能是 sha512。 ex(使用 crypto++ 进行 sha hash + base64 编码)。

          #include <iostream>
          #include <sstream>
          #include <ctime>
          #include <crypto++/sha.h>
          #include <crypto++/base64.h>
          
          int main() {
              std::string digest;
              std::stringstream ss("");
              ss << std::time(NULL);
          
              // borrowed from http://www.cryptopp.com/fom-serve/cache/50.html
              CryptoPP::SHA512 hash;
              CryptoPP::StringSource foo(ss.str(), true,
                  new CryptoPP::HashFilter(hash,
                     new CryptoPP::Base64Encoder(
                         new CryptoPP::StringSink(digest))));
              std::cout << digest << std::endl;
          
              return 0;
          }
          

          【讨论】:

          • 我也是这样。
          【解决方案9】:

          https://github.com/bigfatsea/SUID 简单唯一标识符

          虽然它是用 Java 编写的,但可以轻松移植到任何其他语言。您可能会期望 136 年后在同一实例上出现重复的 ID,这对于中小型项目来说已经足够了。

          例子:

          long id = SUID.id().get();
          

          【讨论】:

            猜你喜欢
            • 2014-11-26
            • 2013-01-05
            • 2013-04-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-03-14
            相关资源
            最近更新 更多