为什么要使用C#类System.Random所有,而不是System.Security.Crypto

2019-06-18 07:26发布

为什么会有人用“标准”的随机数发生器从System.Random可言,而不是始终使用加密安全随机数发生器从System.Security.Cryptography.RandomNumberGenerator (或它的子类,因为RandomNumberGenerator是抽象的)?

内特-劳森告诉我们,在他的谷歌技术讲座演讲“ 加密反击 ”在分13:11不使用“标准”的随机数在Python,Java和C#发电机,改为使用加密安全的版本。

我知道随机数生成器的两个版本之间的差异(见问题101337 )。

但是,理由是那里并不总是使用安全的随机数发生器? 为什么要使用System.Random呢? 性能吧?

Answer 1:

速度和意图。 如果你生成一个随机数,并没有必要的安全,为什么要使用一个缓慢的加密功能? 你不需要安全,何必让别人认为的数量可用于安全的东西时,它会不会?



Answer 2:

除了速度和更有用的接口( NextDouble()等),也可以通过使用固定的种子值以使一个可重复的随机序列。 这是非常有用的,其中包括测试期间他人。

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


Answer 3:

首先演示的链接,你只有用于安全目的的随机数的会谈。 因此,它不要求Random不好非安全的目的。

但我要求它是。 在.NET 4的实施Random在几个方面是有缺陷的。 我建议只使用它,如果你不关心你的随机数的质量。 我建议使用更好的第三方实现。

缺陷1:播种

默认的构造种子与当前时间。 因此的所有实例Random用很短的时间帧(约10ms)的范围内的默认构造函数创建返回相同的序列。 这是记录与“设计”。 如果你想多线程代码,这是特别讨厌的,因为你不能简单地创建一个实例Random在每个线程的执行开始。

解决方法是使用默认的构造函数,并在必要时人工种子时要格外小心。

这里的另一问题是,所述种子的空间是相当小的(31个比特)。 所以,如果你生成的50K情况下, Random与完全随机的种子,你可能会得到随机数的一个序列两次(由于生日悖论 )。 因此,人工播种,不容易得到正确的要么。

缺陷2:通过返回的随机数的分布Next(int maxValue)被偏置

有参数针对Next(int maxValue)显然是不均匀的。 例如,如果你计算r.Next(1431655765) % 2 ,你会得到0的样本的2/3。 (在回答结束示例代码。)

缺陷3: NextBytes()方法是低效的。

的每字节费用NextBytes()是大约一样大的成本,以产生一个完整的整数采样Next() 由此我怀疑他们确实创造每字节一个样本。

一个更好的实施使用3个字节出每个样品的将加速NextBytes()增长了近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);
}


Answer 4:

System.Random是更高性能的,因为它不产生加密安全随机数。

在我的机器的简单测试填充的4个字节的缓冲用随机数据1,000,000次花费49毫秒随机的,但2845毫秒RNGCryptoServiceProvider。 请注意,如果你增加你填充缓冲区的大小,差异缩小为RNGCryptoServiceProvider的开销较少有关。



Answer 5:

最明显的原因已经提及,所以这里是一个比较模糊的一个:加密的PRNG通常需要不断与“真正的”熵补种。 因此,如果您使用的是CPRNG过于频繁,你可能会耗尽系统的熵池,这(取决于CPRNG执行)要么削弱它(从而允许攻击者预测它),或在试图填补它会阻止它的熵池(从而成为DoS攻击的攻击向量)。

无论哪种方式,您的应用程序现在已经成为了这等,完全不相关的应用程序的攻击向量-不像你的-实际上极其依赖于CPRNG的密码特性。

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



Answer 6:

如果你正在编写一个在线游戏卡或LOTTER那么你会希望确保该序列几乎是不可能的猜测。 但是,如果你是显示的用户,比方说,当天的报价性能比安全更重要。



Answer 7:

请注意,在C#中System.Random类编码错误,所以应尽量避免。

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



Answer 8:

这已经在一些长度进行了讨论,但最终,选择RNG时的性能的问题是次要的考虑。 有一个巨大的RNG的排列在那里,罐头莱默LCG,大多数系统的RNG由是不是最好的,也不一定,即使最快的。 在旧的,较慢的系统这是一个很好的妥协。 妥协是很少人真正相关的这些日子。 事情持续到现有系统,主要是因为A)的东西已经建成,并没有真正的理由“推倒重来”在这种情况下,和B)什么的绝大部分人会使用它,它的'够好了'。

最终,一个RNG的选择归结为风险/回报率。 在某些应用中,例如视频游戏,没有任何风险。 一莱默RNG是绰绰有余,而且是小,简洁,快速,易于理解,并且“在盒子”。

如果应用程序是,例如,一个在线扑克游戏或抽奖那里有实际的奖品和涉及真正的金钱进场在某些点上公式中的“盒子”莱默不再足够。 在32位版本,它只有2 ^ 32它充其量开始周期之前可能的有效状态。 这些天来,这是一个开放的大门,蛮力攻击。 在这样的情况下,开发商将要到像某些物种的很长一段 RNG,并可能从一个保密性强的提供商种子吧。 这给了速度和安全性之间的良好平衡。 在这种情况下,人会出来寻找类似的梅森难题 ,或某种形式的多个递归发生器

如果应用程序是一样的东西在网络上传输大量的财务信息,现在有一个巨大的风险,而且它在很大程度上outweights任何的报酬。 还有装甲车,因为有时全副武装的男子是这是足够的唯一保障,相信我,如果特种部队的人有坦克,战斗机和直升机的大队是经济上可行,这将是首选的方法。 在这样的情况下,使用强加密RNG是有道理的,因为无论安全级别,你可以得到,它并不像你想要的。 所以,你拿那么多,你可以找到,而且成本是一个非常,非常遥远的第二位的问题,无论是在时间和金钱。 如果这意味着每一个随机序列需要3秒,产生一种非常强大的计算机上,你要等待3秒,因为在对事物的计划,这是一个微不足道的成本。



Answer 9:

不是每个人都需要加密的安全随机数字,他们可能从速度更快的普通PRNG获益更多。 也许更重要的是,你可以控制System.Random数字序列。

在利用你可能要重新创建随机数的模拟,重新运行使用相同的种子仿真。 当你想再生一个给定的冲突情况,以及它可以很方便的跟踪错误 - 与崩溃的程序随机数完全相同的顺序运行程序。



Answer 10:

如果我不需要的安全性,即,我只想要一个相对不确定的值不是一个是保密性强,随机有一个更容易的接口来使用。



Answer 11:

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



Answer 12:

Random不是一个随机数生成器,它是一个确定性的伪随机序列发生器,其名字的历史原因。

使用理由System.Random是,如果你想这些属性,即确定性序列,这是保证当使用相同的种子初始化产生结果的顺序相同。

如果你想提高“随机性”,而不会牺牲接口,可以继承System.Random覆盖的几种方法。

  • https://msdn.microsoft.com/en-us/library/system.random.sample(v=vs.110).aspx

为什么你会想要一个确定性序列

其中一个原因有确定性的序列,而不是真正的随机性是因为它是重复的。

例如,如果您运行的是数值模拟,你可以初始化一个(真)的随机数序列,并记录使用了什么号码

然后,如果你想重复完全相同的模拟,例如用于调试的目的,你可以通过初始化而不是与记录值序列这么做。

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

我能想到的唯一理由是为了与现有代码的向后兼容性,它使用这个类。

总之,如果你想提高的顺序不改变你的代码的其余部分,继续前进。



Answer 13:

:我写了一个游戏(水晶滑块在iPhone上这里在地图上,将竖起一个“随机”系列的宝石(图像),你怎么想了,你就会转动地图,并选择他们,他们走了)。 - 类似宝石迷阵。 我使用随机的(),并接种具有100ns的周期数,因为手机启动后,一个漂亮的随机种子。

我发现它惊人的,它会产生游戏,是-of 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

多年来,我已经抛出所有的随机数的测试,我可以在此想到的,它过去的所有的人。 我不认为这是任何值作为加密之一,但它以最快的速度返回一个数字作为“回报* P ++;” 直到用完的521位,然后将其运行在位一个快速的过程,以创建新的随机的。

我创建了一个C#包装 - 称之为JPLRandom()实现相同的接口,随机()和改变,我把它称为代码的所有地方。

所不同的是好得多 - OMG我很惊讶 - 不应该有这样我可以从刚才看的图案90级左右的宝石屏幕告诉我们,但我做了我的游戏下面这个紧急释放。

而且,我不会再用不完System.Random()的任何东西。 我很震惊,他们的版本是什么,现在是30岁的交口称赞!

-Traderhut游戏



文章来源: Why use the C# class System.Random at all instead of System.Security.Cryptography.RandomNumberGenerator?