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

Ler*_*rve 77 .net c# random cryptography

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

Nate Lawson 在13:11分钟的Google Tech Talk演讲中告诉我们" Crypto Strikes Back ",不要使用Python,Java和C#中的"标准"随机数生成器,而是使用加密安全版本.

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

但是,有什么理由不总是使用安全随机数发生器?为什么要使用System.Random?性能或许?

Kev*_*che 136

速度和意图.如果您生成一个随机数并且不需要安全性,为什么要使用慢速加密功能?您不需要安全性,那么为什么要让其他人认为这个数字可能用于安全的东西呢?

  • @Kristoffer我觉得你误用了`Random`.让我猜一下:你为每个数字创建了一个新的"Random"类实例,因为它由粗定时器播种,所以在相同的值内播种约1-16ms的间隔. (34认同)
  • 我非常喜欢意图论点. (30认同)
  • @CodesInChaos:除此之外,还有一个带有"Random"的竞争条件,当从多个线程使用相同的对象时,它会返回全0. (14认同)
  • 应该注意的是,Random.GetNext远非擅长在频谱上"扩展"随机数,特别是在线程环境中.我在编写程序时遇到了这个问题,以便从Rand5问题测试Rand7的不同解决方案.刚刚在0到10之间的100000个随机数的快速线程测试中,生成的数字中的82470个为0.我在之前的测试中看到了类似的差异.密码学随机数在数字分布上是非常均匀的.我想我们的教训是始终测试您的随机数据,看它是否"随机"满足您的需求. (12认同)
  • @KristofferL:见上述评论,另见[本答案](http://stackoverflow.com/questions/3049467/is-c-sharp-random-number-generator-thread-safe/11109361#11109361) (3认同)

Hen*_*man 63

除了速度和更有用的界面(NextDouble()等)之外,还可以通过使用固定的种子值来制作可重复的随机序列.这在测试期间非常有用.

Random gen1 = new Random();     // auto seeded by the clock
Random gen2 = new Random(0);    // Next(10) always yields 7,8,7,5,2,....
Run Code Online (Sandbox Code Playgroud)

  • 伊恩贝尔和大卫布拉本在计算机游戏精英中使用了一个随机生成器来创建一个巨大的行星列表及其属性(大小等),内存非常有限.这也依赖于生成器创建一个确定性模式(来自种子) - 加密显然没有提供(按设计).有关于他们如何在这里做到的更多信息:http://wiki.alioth.net /index.php/Random_number_generator书"无限的游戏世界:数学技术" ISBN:1584500581对这种技术的更广泛的讨论. (7认同)
  • 请记住,MSDN不保证跨.NET版本保留此属性:_ ["在.NET Framework的主要版本中,不保证Random类中随机数生成器的实现保持不变."] (http://msdn.microsoft.com/en-us/library/system.random%28v=vs.110%29.aspx)_ (7认同)
  • 并且BitConverter.ToInt32(Byte []值,int startIndex)可能更容易理解.;) (2认同)
  • @phoog _"因此,您的应用程序代码不应该假设相同的种子将在.NET Framework的不同版本中产生相同的伪随机序列."_ - 我不知道,对我来说似乎很清楚.但是,如果他们在不破坏现有程序的情况下不能在实践中改变它,我不会感到惊讶,尽管有这个警告. (2认同)
  • @phoog:你说的是一件事,然后恰恰相反.你直接和自己发生矛盾. (2认同)
  • @Timwi我说的两件事情互相矛盾?我说根据类的契约,相同种子的Random对象的行为必须相同.我这样说是因为我(错误?)理解romkyn的评论说未来的Random实现可能是这样的,相同种子的对象可以返回不同的序列.我还承认,更改框架版本可能会导致返回的特定可重复序列的一次性更改.这与完全丧失可重复性的性质不同. (2认同)

Cod*_*aos 50

首先,您链接的演示文稿仅出于安全目的而谈论随机数.因此,Random对于非安全目的,它不会声称是坏的.

但我确实声称它是..net 4的实现Random在几个方面存在缺陷.如果您不关心随机数的质量,我建议仅使用它.我建议使用更好的第三方实现.

缺陷1:播种

默认构造函数种子与当前时间.因此,Random在短时间范围内(约10ms)使用默认构造函数创建的所有实例都返回相同的序列.这是记录和"按设计".如果你想多线程化你的代码,这尤其令人讨厌,因为你不能简单地Random在每个线程的执行开始时创建一个实例.

解决方法是在使用默认构造函数时要格外小心,并在必要时手动播种.

这里的另一个问题是种子空间相当小(31位).因此,如果你Random使用完全随机的种子生成50k个实例,你可能会得到两个随机数的序列两次(由于生日悖论).所以人工播种也不容易.

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

有些参数Next(int maxValue)显然不均匀.例如,如果您计算,r.Next(1431655765) % 2您将获得0大约2/3的样本.(答案末尾的示例代码.)

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

每字节成本NextBytes()大约与生成完整整数样本的成本一样大Next().由此我怀疑他们确实每字节创建一个样本.

使用每个样本中的3个字节的更好实现将加速NextBytes()几乎3倍.

由于这个缺陷Random.NextBytes()只比System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes我的机器(Win7,Core i3 2600MHz)快约25%.

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


代码示例

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]);
Run Code Online (Sandbox Code Playgroud)

性能:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 嗯.那么你推荐使用Random for C#的实现方式? (5认同)

Mic*_*ael 23

System.Random的性能要高得多,因为它不会生成加密安全的随机数.

在我的机器上用随机数据1,000,000次填充4字节缓冲区的简单测试对于Random来说需要49 ms,对于RNGCryptoServiceProvider需要2845 ms.请注意,如果增加要填充的缓冲区的大小,则差异会缩小,因为RNGCryptoServiceProvider的开销不太相关.

  • 您可能会认为这很苛刻,但是在不包含基准测试代码的情况下发布性能基准测试的结果为-1。即使在过去8年中,Random和RNGCryptoServiceProvider的性能特征没有改变(就我所知,它们可能已经改变了),但我已经看到Stack Overflow上使用了足够多的完全破坏基准测试,以至于不信任代码未公开的基准测试结果。 (3认同)
  • 感谢您通过实际测试进行演示. (2认同)

Jör*_*tag 20

最明显的原因已经提到了,所以这里有一个更加模糊的原因:加密的PRNG通常需要不断地用"真实"熵重新接种.因此,如果您经常使用CPRNG,您可能会耗尽系统的熵池(这取决于CPRNG的实现)会削弱它(从而允许攻击者预测它),或者它会在尝试填充时阻塞它的熵池(因此成为DoS攻击的攻击媒介).

无论哪种方式,您的应用程序现在已经成为其他完全不相关的应用程序的攻击载体 - 与您的应用程序不同 - 实际上在很大程度上依赖于CPRNG的加密属性.

这是一个真实的现实世界问题,BTW,已经在无头服务器上观察到(它自然具有相当小的熵池,因为它们缺少熵源,如鼠标和键盘输入)运行Linux,其中应用程序错误地使用/dev/random内核CPRNG进行各种排序随机数,而正确的行为是从中读取一个小的种子值,/dev/urandom并用它来种子自己 PRNG.

  • 系统时间不是熵源,因为它是可预测的.我不确定空闲字节的数量,但我怀疑它是一个高质量的熵源.通过向服务器发送更多请求,攻击者可以使空闲字节数减少,从而使其部分具有确定性.您的应用程序成为攻击媒介,因为通过耗尽熵池,它会强制其他安全关键应用程序使用较少随机的随机数 - 或等到补充熵源. (3认同)

Dan*_*plo 11

如果您正在编写在线纸牌游戏或彩票,那么您可能希望确保序列几乎无法猜测.但是,如果您向用户显示当天的报价,那么性能比安全性更重要.


evo*_*obe 10

请注意,C#中的System.Random类编码不正确,因此应避免使用.

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

  • 看来该链接已关闭,因为微软取消了“连接”功能。 (3认同)

小智 9

这已在一定程度上进行了讨论,但最终,在选择RNG时,性能问题是次要考虑因素.那里有大量的RNG,大多数系统RNG组成的罐装Lehmer LCG不是最好的,也不一定是最快的.在旧的,缓慢的系统上,这是一个很好的妥协.这些妥协现在很少真正具有相关性.这个东西一直存在于今天的系统中,主要是因为A)这个东西已经建好了,并且没有真正的理由在这种情况下"重新发明轮子",B)因为大部分人都将使用它,它是'够好了'.

最终,RNG的选择归结为风险/回报率.在某些应用中,例如视频游戏,不存在任何风险.Lehmer RNG绰绰有余,小巧,简洁,快速,易于理解,并且"在盒子里".

例如,如果应用程序是在线扑克游戏或彩票,其中涉及实际的奖品,并且在等式中的某个点上真正的金钱发挥作用,那么"在盒子里"Lehmer已不再适用.在32位版本,它只有2 ^ 32就开始周期之前可能的有效状态最好.这些天,这是对暴力攻击敞开的大门.在这种情况下,开发人员会想要某些物种的非常长时间的 RNG,并且可能从加密强大的提供者那里播种.这在速度和安全性之间提供了良好的折衷.在这种情况下,该人将会寻找像Mersenne Twister或某种多重递归生成器这样的东西.

如果应用程序类似于通过网络传递大量财务信息,那么现在存在巨大的风险,并且它会大大超过任何可能的奖励.还有装甲车,因为有时候全副武装的人是唯一足够安全的人,相信我,如果一个拥有坦克,战斗机和直升机的特殊行动人员在经济上可行,那将是首选方法.在这种情况下,使用加密强大的RNG是有道理的,因为无论您获得什么级别的安全性,它都没有您想要的那么多.因此,您可以尽可能多地找到,而且成本是一个非常非常偏远的第二位问题,无论是时间还是金钱.如果这意味着每个随机序列需要3秒才能在非常强大的计算机上生成,那么您将等待3秒钟,因为在方案中,这是一个微不足道的成本.

  • 我认为你的体重是错的; 发送财务数据需要非常快; 如果您的交易算法能够比竞争对手快0.1秒获得结果,那么您在买入/卖出/止损/报价命令的队列中会更好.3秒是永恒的.这就是为什么交易者投资于疯狂的好电脑.见上一个答案; Crypt.RNG每个新号码只需要0,0028毫秒; 0.0000028秒,因此您需要处理多少处理量,以及速度的重要程度为9个数量级. (3认同)