代码之家  ›  专栏  ›  技术社区  ›  Lernkurve

为什么要使用C类System.Random而不是System.Security.Cryptography.RandomNumberGenerator?

  •  73
  • Lernkurve  · 技术社区  · 15 年前

    为什么有人会使用“标准”随机数生成器 System.Random 而不是总是使用密码安全的随机数生成器 System.Security.Cryptography.RandomNumberGenerator (或其子类,因为RandomNumberGenerator是抽象的)?

    Nate Lawson在他的谷歌技术演讲中告诉我们 Crypto Strikes Back “在13:11分钟,不要使用Python、Java和Cype中的“标准”随机数生成器,而是使用加密安全的版本。

    我知道随机数生成器的两个版本之间的区别(参见 question 101337 )

    但是,为什么不总是使用安全随机数生成器呢?为什么要使用系统?随机的?也许是表演?

    13 回复  |  直到 7 年前
        1
  •  121
  •   Cole Tobin Matt    11 年前

    速度和意图。如果您正在生成一个随机数,并且不需要安全性,那么为什么要使用慢速加密函数呢?你不需要安全,那为什么让别人认为这个号码在不安全的时候可以用来做安全的事情呢?

        2
  •  58
  •   Afzaal Ahmad Zeeshan    8 年前

    除了速度和更有用的界面( NextDouble() 等)也可以使用固定的种子值生成可重复的随机序列。这在测试期间非常有用。

    Random gen1 = new Random();     // auto seeded by the clock
    Random gen2 = new Random(0);    // Next(10) always yields 7,8,7,5,2,....
    
        3
  •  44
  •   Mark Amery Harley Holcombe    7 年前

    首先,您链接的演示文稿只讨论出于安全目的的随机数字。所以它没有声称 Random 不利于非安全目的。

    但我确实声称是的。.NET 4实现 随机的 有几个方面的缺陷。我建议只有在你不关心随机数的质量时才使用它。我建议使用更好的第三方实现。

    缺陷1:播种

    默认构造函数使用当前时间种子。因此 随机的 在短时间内(约10毫秒)使用默认构造函数创建,返回相同的序列。这是文件化的,并且是“按设计”的。如果您想多线程地编写代码,这尤其令人恼火,因为您不能简单地创建 随机的 在每个线程执行的开始。

    在使用默认构造函数时要格外小心,并在必要时手动进行种子设定。

    这里的另一个问题是种子空间相当小(31位)。因此,如果您生成 随机的 使用完全随机的种子,您可能会得到一个随机数序列两次(由于 birthday paradox )所以人工播种也不容易正确。

    缺陷2:返回的随机数分布 Next(int maxValue) 有偏见

    有一些参数 下一个(int maxvalue) 显然不统一。例如,如果你计算 r.Next(1431655765) % 2 你会得到 0 在大约2/3的样本中。(答案末尾的示例代码。)

    缺陷3: NextBytes() 方法效率低下。

    每字节成本 NEXBYTESS() 与生成完整整数样本的成本差不多 Next() . 因此,我怀疑它们确实每字节创建一个样本。

    在每个示例中使用3个字节的更好的实现将加快 NEXBYTESS() 几乎上升了一个因素3。

    由于这个缺陷 Random.NextBytes() 只有25%的速度比 System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes 在我的机器上(Win7,酷睿i3 2600MHz)。

    我敢肯定,如果有人检查了源代码/反编译的字节代码,他们会发现比我的黑盒分析发现的更多的缺陷。


    代码样本

    r.Next(0x55555555) % 2 强烈偏向:

    Random r = new Random();
    const int mod = 2;
    int[] hist = new int[mod];
    for(int i = 0; i < 10000000; i++)
    {
        int num = r.Next(0x55555555);
        int num2 = num % 2;
        hist[num2]++;
    }
    for(int i=0;i<mod;i++)
        Console.WriteLine(hist[i]);
    

    性能:

    byte[] bytes=new byte[8*1024];
    var cr=new System.Security.Cryptography.RNGCryptoServiceProvider();
    Random r=new Random();
    
    // Random.NextBytes
    for(int i=0;i<100000;i++)
    {
        r.NextBytes(bytes);
    }
    
    //One sample per byte
    for(int i=0;i<100000;i++)
    {   
        for(int j=0;j<bytes.Length;j++)
          bytes[j]=(byte)r.Next();
    }
    
    //One sample per 3 bytes
    for(int i=0;i<100000;i++)
    {
        for(int j=0;j+2<bytes.Length;j+=3)
        {
            int num=r.Next();
            bytes[j+2]=(byte)(num>>16);   
            bytes[j+1]=(byte)(num>>8);
            bytes[j]=(byte)num;
        }
        //Yes I know I'm not handling the last few bytes, but that won't have a noticeable impact on performance
    }
    
    //Crypto
    for(int i=0;i<100000;i++)
    {
        cr.GetBytes(bytes);
    }
    
        4
  •  23
  •   Michael    15 年前

    因为它不生成加密安全的随机数,所以它的性能要高得多。

    在我的机器上用随机数据填充4个字节的缓冲区1000000次的一个简单测试需要49毫秒的随机时间,而对于RNGCryptoServiceProvider则需要2845毫秒的时间。请注意,如果您增加了正在填充的缓冲区的大小,那么差异就会缩小,因为rngCryptoServiceProvider的开销不那么相关。

        5
  •  19
  •   Jörg W Mittag    15 年前

    最明显的原因已经被提到了,所以这里有一个更模糊的原因:密码过程通常需要用“真实”的熵连续地重新排序。因此,如果过于频繁地使用CPRNG,则可能会耗尽系统的熵池,该熵池(取决于CPRNG的实现)将削弱系统的熵池(从而允许攻击者预测),或者在试图填充熵池时阻塞系统(从而成为DoS攻击的攻击向量)。

    不管怎样,您的应用程序现在已经成为其他完全不相关的应用程序的攻击载体,与您的应用程序不同,这些应用程序实际上非常重要。 依赖 关于CPRNG的加密属性。

    这是一个实际的现实问题,btw,在运行Linux的无头服务器(由于缺少诸如鼠标和键盘输入等熵源,自然具有相当小的熵池)上观察到的,在这些服务器上,应用程序错误地使用了 /dev/random 内核对所有随机数进行CPRNG,而正确的行为是从 /dev/urandom 用它来播种 拥有 PRNG

        6
  •  11
  •   Dan Diplo    15 年前

    如果你正在编程一个在线纸牌游戏或彩票,那么你会想确保序列是旁边的不可能猜测。但是,如果您向用户展示的是当天的报价,那么性能比安全性更重要。

        7
  •  10
  •   evolvedmicrobe    11 年前

    请注意,C中的system.random类编码错误,因此应避免使用。

    https://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug#tabs

        8
  •  9
  •   David    15 年前

    这在一定程度上已经讨论过,但最终,在选择RNG时,性能问题是次要的考虑因素。那里有大量的RNG,大多数系统RNG包含的罐装莱默LCG不是最好的,甚至不一定是最快的。在旧的、缓慢的系统上,这是一个极好的折衷方案。现在这种妥协很少真正有意义。这件事一直存在于当今的系统中,主要是因为a)这件事已经建造好了,在这种情况下没有真正的理由“重新发明轮子”;b)对于绝大多数人将使用它的目的,这是“足够好的”。

    最终,RNG的选择取决于风险/回报率。在一些应用程序中,例如视频游戏,没有任何风险。Lehmer RNG不仅足够,而且小巧、简洁、快速、易于理解,而且“在盒子里”。

    例如,如果应用程序是一个在线扑克游戏或彩票,其中涉及到实际的奖品,并且在方程式的某个点上真正的钱开始发挥作用,那么“在盒子里”的玩家就不再足够了。在32位版本中,在开始循环之前,它只有2^32个可能的有效状态 至多 . 这些天,这是一扇暴力攻击的大门。在这种情况下,开发人员将希望转到类似 很长时间 一些物种的RNG,可能是来自密码强大的提供者。这在速度和安全性之间提供了一个很好的折衷方案。在这种情况下,此人将外出寻找类似 随机数生成 ,或者 多递归生成器 某种类型的

    如果应用程序类似于通过网络通信大量的财务信息,那么现在存在巨大的风险,而且它严重超过了任何可能的回报。现在仍然有装甲车,因为有时全副武装的人是唯一足够的安全保障,相信我,如果一个拥有坦克、战斗机和直升机的特种作战旅在财政上可行的话,这将是选择的方法。在这种情况下,使用加密的强RNG是有意义的,因为无论您能获得什么级别的安全性,它都不如您想要的那么多。所以你会尽你所能,花费是一个非常,非常遥远的第二位问题,无论是时间还是金钱。如果这意味着每一个随机序列在一台功能强大的计算机上需要3秒钟的时间来生成,那么您将等待3秒钟,因为在事物的方案中,这是一个微不足道的成本。

        9
  •  4
  •   nos    15 年前

    并不是每个人都需要加密安全的随机数,而且他们可能从更快速的纯prng中获益更多。也许更重要的是你可以控制系统的序列,随机数。

    在使用可能要重新创建的随机数的模拟中,使用相同的种子重新运行模拟。当你想重新生成一个给定的错误场景时,它可以很方便地跟踪错误——用与程序崩溃完全相同的随机数序列运行你的程序。

        10
  •  2
  •   tvanfosson    15 年前

    如果我不需要安全性,也就是说,我只需要一个相对不确定的值,而不是一个加密性强的值,Random有一个更容易使用的接口。

        11
  •  2
  •   quant_dev    15 年前

    不同的需求需要不同的RNG。对于加密,您希望您的随机数尽可能随机。对于蒙特卡罗模拟,您希望它们均匀地填充空间,并能够从已知状态启动RNG。

        12
  •  2
  •   Ben    8 年前

    Random 它不是一个随机数生成器,而是一个确定性伪随机序列生成器,由于历史原因而得名。

    使用的理由 System.Random 如果需要这些属性,即确定性序列,则保证使用相同的种子初始化时生成相同的结果序列。

    如果您想在不牺牲接口的情况下改进“随机性”,可以继承自 系统随机 重写多个方法。

    你为什么想要一个确定的序列?

    之所以具有确定性序列而不是真正的随机性,是因为它是可重复的。

    例如,如果正在运行数值模拟,可以使用(真)随机数初始化序列,以及 记录使用的号码 .

    然后,如果你想重复 完全相同 模拟,例如出于调试目的,您可以使用初始化序列 记录 价值。

    你为什么要这个特别的,不是很好的序列?

    我能想到的唯一原因是与使用这个类的现有代码向后兼容。

    简而言之,如果您希望在不更改其余代码的情况下改进序列,请继续。

        13
  •  -1
  •   Traderhut Games    11 年前

    我写了一个游戏(iPhone上的Crystal Sliders: Here )这将在地图上放置一系列“随机”的宝石(图像),你将按你想要的方式旋转地图,然后选择它们,它们就消失了。-类似于珠宝。我使用的是random(),它的种子自手机启动以来有100ns的滴答声,这是一个相当随机的种子。

    我找到了它 太神了 它将生成几乎完全相同的游戏-90个左右的宝石,2种颜色,我会得到两个完全相同的除了1到3个宝石!如果你翻转90个硬币,得到相同的图案,除了1-3次翻转,那是非常不可能的!我有几个屏幕截图显示了相同的内容。我对System.Random()有多糟糕感到震惊!我想,我一定是在我的代码中写了严重错误的东西,并且用错了。但我错了,是发电机。

    作为一个实验——也是最后一个解决方案,我回到了自1985年左右以来一直使用的随机数生成器——它要好得多。它更快,重复之前的周期为1.3*10^154(2^521)。最初的算法是用一个16位数字播种的,但是我把它改为32位数字,并改进了初始播种。

    原版在这里:

    ftp://ftp.grnet.gr/pub/lang/algorithms/c/jpl-c/random.c

    这些年来,我把我能想到的每一个随机数测试都抛到脑后,它已经超越了所有的测试。我不希望它有任何值作为加密值,但它返回的数字与“return*p++;”的速度一样快,直到它用完521位,然后它对这些位执行一个快速处理,以创建新的随机位。

    我创建了一个名为jplrandom()的C包装器,实现了与random()相同的接口,并更改了我在代码中调用它的所有位置。

    区别是好得多的-天哪,我很惊讶-我不应该从一个模式看90个左右宝石的屏幕就知道了,但我做了一个紧急释放我的游戏之后。

    我再也不会为任何事情使用System.Random()。我很震惊他们的版本被现在30岁的东西吹走了!

    -TraderHut游戏