如何生成一个较长的全局唯一标识符(GUID)?

15

我想生成一个类似于Gmail会话密钥的长UUID。 它应该至少为256个字符,最多不超过512个字符。 它可以包含所有字母数字字符和一些特殊字符(键盘上功能键下方的字符)。 已经有类似的实现了吗?

使用C ++或C#语言

更新:GUID是不够安全的。我们已经看到碰撞的情况,需要解决。现在512是最大值,因为它将防止我们更改已经发布的内容。

更新2:对于那些坚持GUID的唯一性的人,如果有人想猜测您的下一个会话ID,他们不必计算下一个1万亿年的组合。他们只需要限制时间因素,几个小时就可以完成。


10
全球唯一不够唯一吗? - John Rasch
7
@John: 你的应用程序不需要支持星际帝国吗? - Steven Sudit
16
不论他所问的是否有必要,因为这是一个诚实的问题,对他进行踩票似乎毫无意义。 - Dan Tao
4
为什么不直接回答他的问题,不要带着讽刺和负能量呢? - bl4ckb0l7
1
@Ron https://dev59.com/8HI-5IYBdhLWcg3wu7BU"这将运行的时间比小时还要长。假设它以1Ghz的速度循环(实际上不会那么快,速度要慢得多),它将运行10790283070806014188970年。这大约是宇宙存在的年龄的830亿倍。" - Andrey
显示剩余22条评论
9个回答

31
如果您的GUID发生碰撞,我能问一下您是如何生成它们的吗?GUID基于以下内容,因此它们之间发生碰撞的概率非常小: - 60位 - 生成时的时间戳 - 48位 - 计算机标识符 - 14位 - 唯一ID - 6位是固定的
您必须在完全相同的时间内在同一台机器上运行GUID生成约50次才能有50%的碰撞几率。请注意,时间被测量到纳秒级。
更新:根据您的评论“将GUID放入哈希表中”... 碰撞不是由GUID引起的,而是由GetHashCode()方法引起的。
public override int GetHashCode()
{
    return ((this._a ^ ((this._b << 0x10) | ((ushort) this._c))) ^ ((this._f << 0x18) | this._k));
}

你可以看到它返回一个int,因此如果哈希表中有超过2 ^ 32个“GUIDs”,那么你100%会发生碰撞。


尝试在 SQL Server 2005 中插入。那是我们看到冲突的地方。 - Ron
3
@Ron:你可以让SQL给你生成GUID。 - Matthew Whited
1
这是一个很好的观点。原帖作者应该修改他的测试程序,在发生冲突时检查相等性。这样,您就可以区分具有冲突哈希码的2个GUID和2个相同的GUID。 - A. Levy

16

根据您的更新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整除,存在一些偏差,如果在余数的无人区落地,可以通过丢弃和获取新的随机数来消除偏差。

编辑2-不能保证唯一性,您可以持有一个静态的64位(如果需要更高)单调计数器,将其编码为base46,并将其作为ID的前4-5个字符。

更新-现在保证唯一性。

更新2:算法现在更慢了,但移除了偏差。

编辑:我刚进行了一项测试,我想让您知道ToBase64String可能会返回非字母数字字符(例如1编码为"AQAAAAAAAAA="),以便您知道。

新版本:

Matt Dotson的回答中获取,如果您对键空间不是太担心,那么可以按照这种方式进行,它将运行得快得多。

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);
}

1
从技术上讲,你仍然不能保证是唯一的。当计数器位用尽并开始循环时,你将面临非常遥远的碰撞可能性。如果你要保持一个计数器,那么你可以使用System.Numerics.BigInteger并永远计数。 - Matt Dotson
4
如果他每秒生成11,574个新ID(每天10亿个)24小时不停歇,一年365天,那么需要5050万年才能用完这些ID。我认为64位就足够了。 - Scott Chamberlain
使用您的随机字符串生成方法,例如,用于生成唯一产品密钥是否安全? - David Anderson
@DavidAnderson 是的,但如果产品密钥要在客户端上进行验证,而不连接到服务器以检查其是否有效,则无法工作。对于这种方法,您需要某种方式来验证密钥是否“正确”。请参见此SO问题以了解如何执行此操作。 - Scott Chamberlain
我已经编写并准备好了一个激活服务器,我只是想确保密钥生成算法足够好(我测试了生成100万个唯一密钥,并且没有冲突),所以它看起来是一个很好的方法。 :) 我曾经在Base32Encoding和其他相关事物上苦苦挣扎。 - David Anderson
请查看RubbishSoft LongGuid:http://rubbishsoft.com/libraries/longguid(是的,这只是一个玩笑)。 - Giorgi Chakhidze

10
StringBuilder sb = new StringBuilder();
for (int i = 0; i < HOW_MUCH_YOU_WANT / 32; i++)
   sb.Append(Guid.NewGuid().ToString("N"));
return sb.ToString();

但是为了什么?


8
问题在于“为什么”,而非“如何”。会话ID比GUID更长是无用的,因为它已经足够大,可以防止暴力攻击。
如果您担心预测GUID,请不要担心。与早期的连续GUID不同,V4 GUID是基于RC4的密码学安全的。我所知道的唯一漏洞取决于完全访问生成值的进程的内部状态,因此,如果您只有GUID的部分序列,则无法实现漏洞利用。
如果您很担心,可以生成GUID,并使用诸如SHA-1之类的散列进行哈希处理。但是,这是浪费时间。如果您担心会话劫持,应该查看SSL,而不是这个。

3
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(hex)编码的guid具有更多的值,512^16与512^64相比。
另外,如果你要把它们放在SQL Server中,请确保关闭大小写不敏感。

1

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

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

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


2
后一种技术并不保证唯一性,因为它是随机的。虽然不太可能发生冲突,但也不能保证。 - Will Hartung
1
当然,第一种技术也不能保证唯一性,并且与第二种技术相比更容易冲突。 :) - Stephen Cleary

1
你可能想查看boost的Uuid Library。它支持多种生成器,包括一个随机生成器可能适合你的需求。

0

我会使用std::time()的哈希值,通常是sha512。 例如(使用crypto++进行sha哈希+ 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;
}

我也是这个想法。 - Ron

0

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

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

示例:

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

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