27

我的同事和我正在讨论这些方法中的哪一种用于自动生成用户 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:

我们还使用成熟的方法来生成帖子 ID,与会话令牌不同,它需要看起来很漂亮才能在我们网站的 url 中显示(如http://mywebsite.com/14983336),所以这里不可以选择 guid ,但是仍然要避免碰撞。

4

7 回答 7

53

我正在寻找一个更深入的原因来解释为什么在与 Guid 具有相同的自由度的情况下,熟化的方法更有可能产生碰撞。

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

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

第三,我撒谎。Random不是根据当前时间播种的;它是根据机器活动的时间量播种的。种子是一个 32 位的数字,由于粒度以毫秒为单位,因此只需几周时间即可完成。但这不是问题。问题是:您创建该实例的时间段Random很可能在机器启动后的几分钟内。每次重启机器或在集群中使新机器联机时,都会有一个小窗口在其中创建 Random 实例,并且发生的越多,获得种子的可能性就越大你以前有的。

(更新:.NET 框架的较新版本已经缓解了其中一些问题;在这些版本中,您不再让Random在同一毫秒内创建的每个都具有相同的种子。但是仍然存在许多问题Random;请始终记住,它只是伪-random, not crypto-strength random.Random实际上是非常可预测的,所以如果你依赖不可预测性,它是不合适的。)

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

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

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

于 2013-02-20T16:27:19.913 回答
7

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

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

Guid.NewGuid()会很好,除了我们不想在 url 中使用丑陋的 GUID 和 .NETs 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;
    }
}
于 2013-02-21T11:50:28.087 回答
4

“自动生成用户 ID 和帖子 ID 以在数据库中识别”...为什么不使用数据库序列或身份来生成密钥?

对我来说,您的问题实际上是:“在我的数据库中生成主键的最佳方法是什么?” 如果是这种情况,您应该使用数据库的常规工具,该工具可以是序列或身份。这些比生成的字符串有好处。

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

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

于 2013-02-20T15:22:02.827 回答
3

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

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

Random这意味着无论多长时间,您都不能依赖独特的输出。

RNGCryptoServiceProvider即使您不需要安全性,我也建议使用 CSPRNG ( )。它的性能对于大多数用途来说仍然是可以接受的,我相信它的随机数的质量超过Random. 如果您想要唯一性,我建议您获取大约 128 位的数字。

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


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

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

于 2013-02-20T15:45:21.527 回答
1

使用System.Guid它:

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

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

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

于 2013-02-20T15:21:58.473 回答
1

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

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

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

于 2013-02-20T15:34:35.303 回答
0

关于您的编辑,这是首选 GUID 而不是生成的字符串的原因之一:

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

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

于 2013-02-20T15:58:49.807 回答