为什么有人会使用System.Random中的“标准”随机数生成器,而不是始终使用System.Security.Cryptography.RandomNumberGenerator(或其子类,因为RandomNumberGenerator是抽象的)加密安全随机数生成器?
Nate Lawson在13:11分钟的Google Tech Talk演示文稿“Crypto Strikes Back”中告诉我们,不要使用Python,Java和C#中的“标准”随机数生成器,而是使用加密安全版本。
我知道两个版本的随机数生成器之间的区别(见question 101337)。
但是,有什么理由不总是使用安全随机数生成器?为什么要使用System.Random?表现或许?
答案 0 :(得分:126)
速度和意图。如果您生成一个随机数并且不需要安全性,为什么要使用慢速加密功能?你不需要安全性,那么为什么要让其他人认为这个数字可能用于安全的东西呢?
答案 1 :(得分:60)
除了速度和更有用的界面(NextDouble()
等)之外,还可以通过使用固定的种子值来制作可重复的随机序列。这在测试期间非常有用。
Random gen1 = new Random(); // auto seeded by the clock
Random gen2 = new Random(0); // Next(10) always yields 7,8,7,5,2,....
答案 2 :(得分:46)
首先,您链接的演示文稿仅出于安全目的而谈论随机数。因此,它不会声称Random
对于非安全目的而言是不利的。
但我确实声称是。 Random
的.net 4实现在几个方面存在缺陷。如果您不关心随机数的质量,我建议仅使用它。我建议使用更好的第三方实现。
缺陷1:播种
默认构造函数种子与当前时间。因此,在短时间范围内(大约10ms)使用默认构造函数创建的Random
的所有实例都返回相同的序列。这是记录和“按设计”。如果你想多线程化你的代码,这尤其令人讨厌,因为你不能在每个线程的执行开始时简单地创建Random
的实例。
解决方法是在使用默认构造函数时要格外小心,并在必要时手动播种。
这里的另一个问题是种子空间相当小(31位)。因此,如果您使用完全随机的种子生成50k个Random
个实例,您可能会获得两个随机数的序列两次(由于birthday paradox)。因此手动播种也不容易。
缺陷2:Next(int maxValue)
返回的随机数的分布有偏见
Next(int maxValue)
显然不均匀的参数。例如,如果您计算r.Next(1431655765) % 2
,您将在约2/3的样本中获得0
。 (答案末尾的示例代码。)
缺陷3:NextBytes()
方法效率低下。
NextBytes()
的每字节成本与使用Next()
生成完整整数样本的成本差不多。由此我怀疑他们确实为每个字节创建了一个样本。
使用每个样本中的3个字节的更好实现将使NextBytes()
的速度提高几乎3倍。
由于这个缺陷,Random.NextBytes()
只比我机器上的System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes
快了约25%(Win7,Core 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);
}
答案 3 :(得分:23)
System.Random性能更高,因为它不会生成加密安全的随机数。
对我的机器进行简单测试,填充4字节缓冲区,随机数据1,000,000次,随机需要49 ms,RNGCryptoServiceProvider需要2845 ms。请注意,如果增加要填充的缓冲区的大小,则差异会缩小,因为RNGCryptoServiceProvider的开销不太相关。
答案 4 :(得分:19)
最明显的原因已经提到了,所以这里有一个比较模糊的原因:加密PRNG通常需要不断地用“真实”熵重新接种。因此,如果您经常使用CPRNG,您可能会耗尽系统的熵池(这取决于CPRNG的实现)会削弱它(从而允许攻击者预测它),或者它会在尝试填充时阻塞它的熵池(因此成为DoS攻击的攻击媒介)。
无论哪种方式,您的应用程序现在已经成为其他完全不相关的应用程序的攻击媒介 - 与您的应用程序不同 - 实际上依赖来确定CPRNG的加密属性。
这是一个真实的现实世界问题,BTW,已经在无头服务器上观察到了(它们自然具有相当小的熵池,因为它们缺少熵源,如鼠标和键盘输入)运行Linux,其中应用程序错误地使用{{ 1}}内核CPRNG用于各种随机数,而正确的行为是从/dev/random
读取一个小的种子值,并使用它来为自己的 PRNG播种。
答案 5 :(得分:11)
如果您正在编写在线纸牌游戏或彩票,那么您可能希望确保序列几乎无法猜测。但是,如果您向用户显示当天的报价,那么表现比安全更重要。
答案 6 :(得分:10)
请注意,C#中的System.Random类编码错误,因此应避免使用。
https://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug#tabs
答案 7 :(得分:9)
这已在一定程度上进行了讨论,但最终,在选择RNG时,性能问题是次要考虑因素。那里有大量的RNG,大多数系统RNG组成的罐装Lehmer LCG不是最好的,也不一定是最快的。在旧的,缓慢的系统上,这是一个很好的妥协。这些妥协现在很少真正具有相关性。这个东西一直存在于今天的系统中,主要是因为A)这个东西已经建好了,并且没有真正的理由在这种情况下“重新发明轮子”,B)因为大部分人都将使用它,它是'够好'。
归根结底,RNG的选择归结为风险/回报率。在某些应用中,例如视频游戏,不存在任何风险。 Lehmer RNG绰绰有余,小巧,简洁,快速,易于理解,并且“在盒子里”。
如果应用程序是,例如,在线扑克游戏或彩票,其中涉及实际奖品,真正的钱在等式中的某个点发挥作用,“在盒子里”Lehmer不再适用。在32位版本中,它在开始循环最佳之前只有2 ^ 32个可能的有效状态。这些天,这是对暴力攻击敞开的大门。在这样的情况下,开发人员会想要某些物种的很长时间 RNG,并且可能从加密强大的提供商那里获取它。这在速度和安全性之间提供了良好的折衷。在这种情况下,这个人会出去寻找类似 Mersenne Twister 或某种多个递归生成器的东西。
如果应用程序类似于通过网络传递大量财务信息,那么现在存在巨大的风险,并且它会大大超过任何可能的奖励。还有装甲车,因为有时候全副武装的人是唯一足够安全的人,相信我,如果一个拥有坦克,战斗机和直升机的特殊行动人员在经济上可行,那将是首选方法。在这种情况下,使用加密强RNG是有道理的,因为无论您获得什么级别的安全性,它都没有您想要的那么多。因此,您可以尽可能多地找到,而且成本是一个非常非常偏远的第二位问题,无论是时间还是金钱。如果这意味着每个随机序列需要3秒才能在非常强大的计算机上生成,那么您将等待3秒钟,因为在方案中,这是一个微不足道的成本。
答案 8 :(得分:4)
并非每个人都需要加密安全的随机数,并且他们可能从更快速的普通prng中受益更多。也许更重要的是,您可以控制System.Random数字的顺序。
在使用随机数的模拟中,您可能需要重新创建,然后使用相同的种子重新运行模拟。当你想要重新生成一个给定的错误场景时,它可以很方便地跟踪错误 - 用崩溃程序的完全相同的随机数序列运行你的程序。
答案 9 :(得分:2)
如果我不需要安全性,即,我只想要一个相对不确定的值,而不是一个加密强的值,Random有一个更容易使用的接口。
答案 10 :(得分:2)
不同的需求需要不同的RNG。对于加密,您希望随机数尽可能随机。对于蒙特卡罗模拟,您希望它们均匀地填充空间并能够从已知状态启动RNG。
答案 11 :(得分:2)
Random
不是随机数生成器,它是一个确定性的伪随机序列生成器,由于历史原因而得名。
使用System.Random
的原因是,如果您需要这些属性,即确定性序列,当使用相同的种子初始化时,可以保证产生相同的结果序列。
如果要在不牺牲界面的情况下改善“随机性”,可以继承System.Random
覆盖多种方法。
为什么需要确定性序列
具有确定性序列而不是真正随机性的一个原因是因为它是可重复的。
例如,如果您正在运行数值模拟,则可以使用(真)随机数初始化序列,记录使用的数字。
然后,如果您希望重复完全相同的模拟,例如出于调试目的,您可以通过使用记录的值初始化序列来实现此目的。
为什么你想要这个特别的,不是很好的序列
我能想到的唯一原因是向后兼容使用此类的现有代码。
简而言之,如果您想在不更改其余代码的情况下改进序列,请继续。
答案 12 :(得分:-1)
我写了一个游戏(iPhone上的水晶滑块:Here)会随机出现一个&#34;随机的&#34;地图上的一系列宝石(图像),您可以按照您想要的方式旋转地图并选择它们,然后它们就会消失。 - 与宝石迷阵相似。我使用的是Random(),自从手机启动以来播种的数量为100ns,这是一个非常随机的种子。
我发现太棒了它会生成彼此几乎相同的游戏 - 在2种颜色的90个左右的宝石中,我会得到两个完全相同的除了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
多年来,我已经抛出了我能想到的每一个随机数测试,并且它已经过去了。我不希望它具有任何加密价值,但它返回的数字和&#34; return * p ++;&#34;直到它耗尽了521位,然后它对位进行快速处理以创建新的随机位。
我创建了一个C#包装器 - 称为JPLRandom()实现了与Random()相同的接口,并更改了我在代码中调用它的所有位置。
差别非常好 - 我感到非常惊讶 - 我只能在一个模式中看到90个左右的宝石屏幕时无法判断,但是我在此之后紧急发布了我的游戏。 / p>
我永远不会再使用System.Random()了。我震惊了他们的版本被现在已有30年历史的东西所震撼!
-Traderhut Games