Guid.NewGuid()VS来自Random.Next()的随机字符串生成器

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

一个选项使用Random的单个实例,并采用一些有用的参数,因此它可以重用于各种string-gen案例(即从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表没有自动增量(或类似)function来生成密钥。
  • 这里的一些答案只是告诉我使用NewGuid()“因为这就是它的用途”。 我正在寻找一个更深入的理由,为什么煮熟的方法可能更容易产生碰撞,给予与Guid相同的自由度。

编辑2:

我们也使用煮熟的方法来生成postID,与会话令牌不同,它需要在我们网站的url(如http://mywebsite.com/14983336 )中显示相当漂亮,所以guids不是这里的选项但是仍然要避免碰撞。

我正在寻找一个更深入的理由,为什么煮熟的方法可能更容易产生碰撞,给予与Guid相同的自由度。

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

其次,根据当前时间播种Random 。 在同一毫秒内创建的两个Random实例(回想一下,毫秒是现代硬件上的几百万个处理器周期)将具有相同的种子,因此将产生相同的序列。

第三,我骗了。 Random不根据当前时间播种; 它是根据机器活动的时间量播种的。 种子是一个32位的数字,由于粒度是以毫秒为单位,所以只有几个星期,直到它回绕。 但那不是问题; 问题是: 创建该Random实例的时间段很可能在机器启动后的几分钟内。 每次你给机器重新上电,或者让一台新机器在一个集群中联机时,就会有一个小窗口,其中创建了Random实例,发生的越多,你获得种子的几率就越大你以前有过的。

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

最后,如果您有兴趣了解guids的使用和滥用,那么您可能想阅读我的“guid guide”系列; 第一部分在这里:

http://blogs.msdn.com/b/ericlippert/archive/2012/04/24/guid-guide-part-one.aspx

正如其他答案所述,我的实施有一些严重的问题:

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

Guid.NewGuid()会没问题,除非我们不想在URL和.NET中使用丑陋的GUID,因此不知道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和post id以便在数据库中进行标识”…为什么不使用数据库序列或标识来生成密钥?

对我来说,你的问题确实是,“在我的数据库中生成主键的最佳方法是什么?” 如果是这种情况,您应该使用数据库的常规工具,它将是序列或标识。 这些优于生成的字符串。

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

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

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

  1. 它使用Random的全局实例,但不使用锁定。 =>multithreading访问可能会破坏其状态。 在此之后,输出将比现在更糟糕。
  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可能会发生冲突(虽然机会非常小),因此您应该使用数据库自​​己的function为您提供唯一标识符(例如自动增量ID列)。 此外,您无法轻松将GUID转换为4或20(字母)数字。

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

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

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

关于您的编辑,以下是优先于生成的字符串使用GUID的一个原因:

SQL Server中GUID(uniqueidentifier)的本机存储是16个字节。 要存储等效长度的varchar(字符串),其中id中的每个“数字”都存储为一个字符,需要介于32到38个字节之间,具体取决于格式。

由于它的存储,SQL Server也能够比varchar列更有效地索引uniqueidentifier列。