Guid.NewGuid()与random.Next()中的随机字符串生成器进行比较

本文关键字:比较 字符串 NewGuid random Next Guid 随机 | 更新日期: 2023-09-27 18:00:13

我的同事和我正在讨论使用以下哪种方法自动生成用户ID和发布ID以在数据库中进行识别:

一个选项使用Random的单个实例,并采用一些有用的参数,因此它可以用于各种字符串生成情况(即从4位数字引脚到20位字母数字ID)。这是代码:

// This is created once for the lifetime of the server instance
class RandomStringGenerator
{
    public const string ALPHANUMERIC_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    public const string ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public const string NUMERIC = "1234567890";
    Random rand = new Random();
    public string GetRandomString(int length, params char[] chars)
    {
        string s = "";
        for (int i = 0; i < length; i++)
            s += chars[rand.Next() % chars.Length];
        return s;
    }
}

另一种选择是简单地使用:

Guid.NewGuid();

请参阅MSDN 上的Guid.NewGuid

我们都知道Guid.NewGuid()可以满足我们的需求,但我更愿意使用自定义方法。它做同样的事情,但有更多的控制。

我的同事认为,因为自定义方法是我们自己设计的,所以更有可能产生冲突。我承认我并不完全知道Random的实现,但我认为它和Guid.NewGuid()一样随机。自定义方法的典型用法可能是:

RandomStringGenerator stringGen = new RandomStringGenerator();
string id = stringGen.GetRandomString(20, RandomStringGenerator.ALPHANUMERIC_CAPS.ToCharArray());

编辑1:

  • 我们使用的Azure表没有用于生成密钥的自动递增(或类似)功能
  • 这里的一些答案只是告诉我使用NewGuid(),"因为这就是它的用途"。我正在寻找一个更深入的原因,解释为什么在给定与Guid相同的自由度的情况下,精心设计的方法更有可能产生碰撞

编辑2:

我们还使用了伪造的方法来生成post-ID,与会话令牌不同,post-ID需要在我们网站的url中显示得很漂亮(比如http://mywebsite.com/14983336),因此guid在这里不是一个选项,但是仍然需要避免冲突。

Guid.NewGuid()与random.Next()中的随机字符串生成器进行比较

我正在寻找一个更深入的原因,解释为什么在给定与Guid相同的自由度的情况下,精心设计的方法更有可能产生碰撞。

首先,正如其他人所指出的,Random不是线程安全的;从多个线程使用它可能会导致它破坏其内部数据结构,从而始终生成相同的序列。

第二,基于当前时间对Random进行播种。在同一毫秒内创建的两个Random实例(回想一下,在现代硬件上,一毫秒是数百万个处理器周期)将具有相同的种子,因此将产生相同的序列。

第三,我撒谎了。CCD_ 5不是基于当前时间而被播种的;它是根据机器激活的时间量进行播种的。种子是一个32位的数字,由于粒度以毫秒为单位,所以只有几周的时间才能完成。但这不是问题所在;问题是:创建Random实例的时间段很可能在机器启动后的几分钟内每次你给一台机器通电,或者在集群中让一台新机器联机时,都会有一个小窗口来创建Random的实例,这种情况发生得越多,你获得以前的种子的几率就越大。

(更新:.NET框架的较新版本已经缓解了其中的一些问题;在这些版本中,你不再让在同一毫秒内创建的每个Random都有相同的种子。然而,Random仍然有很多问题;请记住,它只是伪随机的,而不是加密强度随机的。Random实际上是非常可预测的,所以如果你依赖于不可预测性,这是不合适的。)

正如其他人所说:如果你想要数据库的主键,那么让数据库为你生成主键;让数据库完成它的工作。如果您想要全局唯一的标识符,则使用guid;这就是他们的目的。

最后,如果你有兴趣了解更多关于指南的使用和滥用,那么你可能想阅读我的";guid-guide";系列第一部分在这里:

https://ericlippert.com/2012/04/24/guid-guide-part-one/

正如其他答案中所写的,我的实现遇到了一些严重的问题:

  • 线程安全:随机不是线程安全的
  • 可预测性:由于Random类的性质,该方法不能用于会话令牌等安全关键标识符
  • 冲突:尽管该方法创建了20个"随机"数,但由于种子值仅为31位,并且来自错误源,因此冲突的概率不是(number of possible chars)^20。给定相同的种子,序列的任何长度都是相同的

Guid.NewGuid()也可以,只是我们不想在url和中使用难看的GUID。NET的NewGuid()算法在会话令牌中使用时并不安全,如果知道一点信息,它可能会给出可预测的结果。

这是我们现在使用的代码,它是安全、灵活的,据我所知,如果有足够的长度和字符选择,就不太可能产生冲突:

class RandomStringGenerator
{
    RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
    public string GetRandomString(int length, params char[] chars)
    {
        string s = "";
        for (int i = 0; i < length; i++)
        {
            byte[] intBytes = new byte[4];
            rand.GetBytes(intBytes);
            uint randomInt = BitConverter.ToUInt32(intBytes, 0);
            s += chars[randomInt % chars.Length];
        }
        return s;
    }
}

"自动生成用户ID和发布ID以在数据库中进行标识"。。。为什么不使用数据库序列或标识来生成密钥呢?

对我来说,你的问题实际上是,"在我的数据库中生成主键的最佳方法是什么?"如果是这样的话,你应该使用数据库的传统工具,它要么是序列,要么是身份。与生成的字符串相比,这些字符串具有优势。

  1. 序列/身份索引更好。有许多文章和博客文章解释了为什么GUID等索引很差
  2. 保证它们在表中是唯一的
  3. 它们可以通过并发插入安全地生成,而不会发生冲突
  4. 它们易于实现

我想我的下一个问题是,您考虑GUID或生成字符串的原因是什么?您会跨分布式数据库进行集成吗?如果没有,你应该问问自己,你是否正在解决一个根本不存在的问题。

您的自定义方法有两个问题:

  1. 它使用Random的全局实例,但不使用锁定。=>多线程访问可能会破坏其状态。在这之后,输出将比它已经吸收更多
  2. 它使用可预测的31位种子。这有两个后果:
    • 在不可访问性很重要的情况下,您不能将其用于任何与安全相关的内容
    • 小种子(31位)会降低数字的质量。例如,如果同时创建多个Random实例(自系统启动以来),它们可能会创建相同的随机数序列

这意味着您不能依赖于Random的输出是唯一的,无论它有多长

我建议您使用CSPRNG(RNGCryptoServiceProvider),即使您不需要安全性。它的性能对于大多数用途来说仍然是可以接受的,而且我相信它的随机数在Random上的质量。如果你想要唯一性,我建议你使用128位左右的数字。

要使用RNGCryptoServiceProvider生成随机字符串,您可以看看我对如何在C#中生成随机的8个字符的字母数字字符串的回答?。


现在Guid.NewGuid()返回的GUID是版本4的GUID。它们是从PRNG生成的,因此它们具有与生成随机122比特数非常相似的特性(其余6比特是固定的)。它的熵源比Random使用的熵源质量高得多,但不能保证它在加密方面是安全的。

但是生成算法可以随时更改,所以你不能依赖它。例如,在过去,Windows GUID生成算法从v1(基于MAC+时间戳)更改为v4(随机)。

按原样使用System.Guid

可以在所有需要唯一标识符的计算机和网络上使用。

注意,Random伪随机数生成器。它不是真正随机的,也不是独一无二的。与128位的GUID相比,它只有32位的值可供使用。

然而,即使是GUID也可能发生冲突(尽管可能性非常小),因此您应该使用数据库自己的功能来为您提供唯一的标识符(例如,自动递增ID列)。此外,您不能轻易地将GUID转换为4或20(字母)数字。

与一些人在评论中所说的相反,GUID.NewGuid()生成的GUID不依赖于任何特定于机器的标识符(只有类型1的GUID是,GUID.NNewGuid(()返回类型4的GUID,这大多是随机的)。

只要您不需要加密安全性,Random类就应该足够好,但如果您想更加安全,请使用System.Security.Cryptography.RandomNumberGenerator。对于Guid方法,请注意并非Guid中的所有数字都是随机的。引用自维基百科:

在规范表示xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx中,N的最高有效位表示变体(取决于变体;使用一位、两位或三位)。UUID规范所涵盖的变体由N的两个最高有效位表示为10(即,十六进制N将始终为8、9、A或B)。在UUID规范所涵盖的变体中,有五个版本。对于该变体,M的四位表示UUID版本(即,十六进制M将是1、2、3、4或5)。

关于您的编辑,有一个原因更喜欢GUID而不是生成的字符串:

SQL Server中GUID(唯一标识符)的本机存储为16字节。要存储相等长度的varchar(字符串),其中id中的每个"数字"都存储为一个字符,需要32到38个字节,具体取决于格式。

由于其存储能力,SQL Server还能够比varchar列更有效地索引uniqueidentifier列。