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


85

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

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

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

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


7
您想输入哪一个?
Macha

13
太多的人认真地将其用作所做工作的理由(通常不大声说)。对代码的理解远胜于对代码的理解,谁在乎琐细的长度差异?
Mark Sowul 2012年

3
但是无论如何,如果您不进行加密,为什么还要使用加密RNG?
马克·索沃

3
@Macha,这就是别名的意思->using R = System.Security.Cryptography.RandomNumberGenerator; R.Create();
cchamberlain

Answers:


144

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


30
我非常喜欢intent的论点。
Lernkurve'2009年

12
应该注意的是,Random.GetNext远不擅长在频谱上“散布”随机数,尤其是在线程环境中。在编写程序来测试来自Rand5问题的Rand7的不同解决方案时,我遇到了这个问题。刚才在快速线程测试中,有100000个介于0到10之间的随机数,其中82470个生成的数字为0。在以前的测试中,我看到了类似的差异。密码术随机数分布非常均匀。我想这课是要经常测试您的随机数据,以确保它足够“随机”以满足您的需求。
克里斯托弗·L

35
@Kristoffer我想你滥用了Random。让我猜测:您Random为每个数字创建了该类的新实例,由于该类是由粗略计时器播种的,因此将以相同的值播种约1-16ms的时间间隔。
CodesInChaos 2011年

15
@CodesInChaos:除此之外,还有一个竞争条件,Random当从多个线程中使用同一对象时,它会导致其返回全0。
BlueRaja-Danny Pflughoeft 2012年

3
@KristofferL:看到上面的评论,也看到这个答案
BlueRaja-Danny Pflughoeft 2012年

65

除了速度和更有用的界面(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
还有BitConverter.ToInt32(Byte [] value,int startIndex)可能更容易理解。;)
sisve

7
伊恩·贝尔(Ian Bell)和大卫·布拉本(David Braben)在电脑游戏“精英”中使用随机生成器,以非常有限的内存创建了大量的行星及其属性(大小等)列表。这也依赖于生成器(从种子)创建确定性模式-加密货币显然不提供(通过设计)。在此处,有关它们如何执行的更多信息: wiki.alioth.net/index.php / Random_number_generator 和《无限游戏宇宙:数学技术》一书ISBN:1584500581对此类技术进行了更一般的讨论。
Daniel James Bryars 2010年


2
@phoog “因此,您的应用程序代码不应假定相同的种子将在.NET Framework的不同版本中导致相同的伪随机序列。” -我不知道,对我来说似乎很清楚。但是,即使有这个警告,如果他们在不破坏现有程序的情况下不能在实践中进行更改,我也不会感到惊讶。
罗曼·斯塔科夫

2
@phoog:您说的是一件事,然后恰恰相反。你直接在矛盾自己。
Timwi'4

53

首先,您链接的演示仅讨论出于安全目的的随机数。因此,Random对于非安全性目的,它并不声称是有害的。

但我确实是这样。.net 4的实现Random存在多种缺陷。我建议仅在不关心随机数质量的情况下使用它。我建议使用更好的第三方实现。

缺点1:播种

默认构造函数以当前时间作为种子。因此,Random在短时间内(约10毫秒)内使用默认构造函数创建的所有实例均返回相同的序列。这是有记录的,是“按设计”的。如果您想对代码进行多线程处理,这会特别烦人,因为您不能简单地创建的实例。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]);

性能:

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);
}

1
有趣的是,可以确认您的发现:在我的机器上,Next(1431655765)也提供2/3的种子。1431655765的魔力是什么?您是怎么得出这个数字的?
citykid 2014年

1
@citykid以十六进制或位的形式查看数字。它的神奇之处在于Random将31位整数转换为具有指定上限的数字的可疑方式。我忘记了细节,但是有点像randomValue * max / 2^{31}
CodesInChaos 2014年

1431655765_10 = 1010101010101010101010101010101_2
Tim

6
嗯 那么,您建议使用哪种随机C#实现?
2014年

1
Next()您在这里证明的神圣牛分布的不均匀性是一个非常壮观的错误-在您第一次写下您的发现6年后,今天仍然存在。(我说的是“ bug”,而不仅仅是“ flaw”,因为文档声称“伪随机数是从有限的一组数字中以相同的概率选择的并非如此,您的代码在此证明了这一点。)
马克·阿默里

24

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

在我的计算机上进行的简单测试用随机数据1,000,000次填充4字节的缓冲区,对于Random来说需要49毫秒,而对于RNGCryptoServiceProvider则需要2845毫秒。请注意,如果增加要填充的缓冲区的大小,则差异会缩小,因为RNGCryptoServiceProvider的开销不太重要。


2
感谢您通过实际测试进行演示。
Lernkurve'2009年

3
您可能会认为这很苛刻,但是在不包含基准测试代码的情况下发布性能基准测试的结果为-1。即使过去8年的性能特征RandomRNGCryptoServiceProvider没有改变(就我所知,它们可能都没有改变过),我也已经看到Stack Overflow上使用了足够多的完全破坏的基准测试,不相信其代码的基准测试结果不公开可用。
马克·阿默里

21

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

无论哪种方式,您的应用程序现在都已成为其他完全不相关的应用程序的攻击载体,与您的应用程序不同,这些应用程序实际上非常依赖于CPRNG的加密属性。

这是一个实际的现实问题,即BTW,在运行Linux的无头服务器(自然而然的熵池较小,因为它们缺少诸如鼠标和键盘输入之类的熵源)中已经观察到,其中应用程序错误地使用了/dev/random内核CPRNG随机数,而正确的行为是从中读取一个较小的种子值,/dev/urandom并使用该值播种自己PRNG。


我阅读了Wikipedia文章和其他一些有关熵和熵耗竭的Internet资料,但我不太理解。当向随机数生成器提供系统时间,可用字节数等信息时,如何耗尽熵池?其他人如何使用它作为攻击向量来预测随机数?您能举个简单的例子吗?也许此讨论必须脱机进行。en.wikipedia.org/wiki/Entropy_%28computing%29
Lernkurve

3
系统时间不是熵源,因为它是可预测的。我不确定可用字节数,但我怀疑它还是一个高质量的熵源。通过向服务器发送更多请求,攻击者可以导致空闲字节数减少,从而使其具有部分确定性。您的应用程序成为攻击媒介,因为通过耗尽熵池,它迫使另一个对安全性要求较高的应用程序使用随机性较小的随机数-或等到熵源得到补充。
–quant_dev

我了解到,如果有人拥有一个伪随机生成器,例如,其中填充了32位种子,那么蛮力攻击通常会很容易。即使是64位种子也可能遭受生日攻击。但是,一旦种子变得比那大得多,我就看不到风险了。如果有一个随机生成器,该随机生成器的每个输出字节通过一个块加密算法传递一个128位状态,然后输出低8位,那么即使有连续输出字节的演出,攻击者也无法推断出该状态,而没有弱点加密算法本身?
2013年

11

如果您正在编写在线纸牌游戏或彩票,则需要确保序列几乎无法猜测。但是,例如,如果要向用户显示报价,则性能比安全性更为重要。


9

已经对此进行了详细讨论,但是最终,性能问题是选择RNG时的次要考虑因素。那里有各种各样的RNG,大多数系统RNG组成的罐装Lehmer LCG并不是最好的,甚至不一定是最快的。在旧的慢速系统上,这是一个很好的折衷方案。如今,这种妥协很少真正有用。这个东西仍然存在于当今的系统中,主要是因为A)这个东西已经被制造出来了,在这种情况下没有真正的理由“重新发明轮子”,并且B)出于绝大多数人将它用于什么目的,这是'够好了'。

最终,RNG的选择归结为风险/回报率。在某些应用程序中,例如视频游戏,没有任何风险。Lehmer RNG绰绰有余,而且体积小巧,简洁,快速,易于理解且“实用”。

例如,如果应用程序是在线扑克游戏或彩票,其中涉及实际奖金,而真钱在等式中的某个时刻起作用,那么“盒中”雷曼不再足够了。在32位版本中,在充其量才开始循环之前,它只有2 ^ 32个可能的有效状态。如今,这是蛮力攻击的门户。在这种情况下,开发人员将希望使用某些物种的“非常长时间” RNG,并可能从具有强大密码学的提供者那里进行种子植入。这在速度和安全性之间做出了很好的折衷。在这种情况下,该人将寻找诸如Mersenne Twister之类的东西,或某种形式的多递归生成器

如果应用程序就像通过网络交流大量财务信息之类的东西,那么现在存在巨大的风险,并且它大大超过了可能的回报。仍然有装甲车,因为有时全副武装的士兵是唯一可以满足需要的安全措施,请相信我,如果一个由特种作战人员组成的大队配备坦克,战斗机和直升机在财务上可行,那将是选择的方法。在这种情况下,使用具有加密功能的RNG很有意义,因为无论您可以获得什么安全级别,它都不是您想要的。因此,您将花掉尽可能多的钱,而成本是一个非常非常遥远的第二问题,无论是时间还是金钱。如果这意味着在一个功能非常强大的计算机上生成每个随机序列都需要3秒,那么您将等待3秒,


3
我认为您的幅度是错误的;发送财务数据的速度必须非常快;如果您的交易算法能够比竞争对手更快地获得0.1ms的结果,那么您在买入/卖出/止损/报价命令队列中的表现会更好。3秒是永恒的。这就是为什么交易者投资疯狂的好计算机的原因。参见前面的答案;每个新编号的Crypt.RNG仅需要0.0028毫秒;0.0000028秒,因此在处理量以及速度的重要性方面,您减少了9个数量级。
亨里克(Henrik)2010年


4

并非每个人都需要加密安全的随机数,而且他们可能会受益于更快的普通打印。也许更重要的是,您可以控制System.Random编号的顺序。

在可能需要重新创建的利用随机数的模拟中,您使用相同的种子重新运行模拟。当您还想重新生成给定的错误情况时,它也可以很方便地跟踪错误-使用完全相同的随机数序列运行程序,使程序崩溃。


2

如果我不需要安全性,也就是说,我只想要一个相对不确定的值,而不是一个加密强度高的值,那么Random会更易于使用。


2

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


1
如果只有System.Random可以的话..哦,很好。
user2864740 '16

2

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

使用的原因System.Random是是否需要这些属性,即确定性序列,当使用相同的种子初始化时,可以保证产生相同的结果序列。

如果要在不牺牲接口的情况下改善“随机性”,则可以从System.Random覆盖多种方法中继承。

为什么要确定性序列

具有确定性序列而非真正随机性的原因之一是因为它是可重复的。

例如,如果您正在运行数值模拟,则可以使用(true)随机数初始化序列,并记录所使用的数字

然后,如果希望重复完全相同的仿真(例如出于调试目的),则可以通过使用记录的值初始化序列来进行重复。

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

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

简而言之,如果您想改善顺序而不更改其余代码,请继续。


1

我编写了一个游戏(iPhone上的晶体滑块: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位,然后对这些位进行快速处理以创建新的随机位。

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

两者之间的差异要好得多-OMG,我很惊讶-从以某种图案观察90颗左右的宝石的屏幕上我无法分辨出什么,但是我随后紧急发布了我的游戏。

而且我再也不会使用System.Random()了。我很震惊,他们的版本被现在已有30年历史的东西吹走了!

-Traderhut游戏


3
我的第一个猜测是,您创建的Random频率太高了。仅应多次调用Next该实例一次才创建它。Random是坏的,但不能坏。您可以将示例程序与出现此问题的一对种子一起发布吗?
CodesInChaos 2014年

该代码将在每个级别的开头创建一个Random()(但与以后的级别相比,它是第1级的主要问题)。代码大致如下:
Traderhut Games 2014年

Rnd =新的Random((uint)GameSeed); NextGameSeed = Rnd.Next(2000000000); 每个级别使用一个由新种子创建的新随机数-为每个级别保存了种子,这样我就可以重新创建地图,并确认匹配的随机种子的序列。这使我可以确认游戏是已解决的有效地图系列,然后重新创建游戏。
Traderhut Games 2014年

最初,根据System.DateTime.Now.Ticks(或0)创建Random,然后使用与上述Rnd.Next()相同的调用选择GameSeed。如果我做不到,那么随机数生成器的播种就会出现严重问题。
Traderhut Games 2014年

这不是原始问题的答案!
Mike Dinescu'3

-1

由于System.Random因其“不正确”和偏见而在这里受到抨击,因此我检查了一下自己。

分配

此f#代码演示了它的行为-在我的平均机器上:

let r = System.Random()
Seq.init 1000000 (fun _ -> r.Next(0,10))
|> Seq.toList
|> Seq.groupBy id
|> Seq.map (fun (v,ls) -> v, ls |> Seq.length)
|> Seq.sortBy fst
|> Seq.iter (printfn "%A")

(0, 100208)
(1, 99744)
(2, 99929)
(3, 99827)
(4, 100273)
(5, 100280)
(6, 100041)
(7, 100001)
(8, 100175)
(9, 99522)    

框架版本,机器,操作系统都可能有所不同。在您的计算机上以F#交互式输入代码,然后尝试一下。我在阅读《密码学》时

let arr = [| 0uy |]
let rr = System. Security.Cryptography.RandomNumberGenerator.Create()
Seq.init 1000000 (fun _ -> rr.GetBytes(arr); arr.[0])
|> Seq.toList
|> Seq.groupBy id
|> Seq.map (fun (v,ls) -> v, ls |> Seq.length)
|> Seq.sortBy fst
|> Seq.take 10 // show first 10 bytes
|> Seq.iter (printfn "%A")

// distribution of first 10 bytes
(0uy, 3862)
(1uy, 3888)
(2uy, 3921)
(3uy, 3926)
(4uy, 3948)
(5uy, 3889)
(6uy, 3922)
(7uy, 3797)
(8uy, 3861)
(9uy, 3874)

性能

#time

let arr = [| 0uy |]

let r = System.Random()
Seq.init 1000000 (fun _ -> r.NextBytes(arr); arr.[0] |> int64) |> Seq.sum

Real: 00:00:00.204, CPU: 00:00:00.203, GC gen0: 45, gen1: 1, gen2: 1
val it : int64 = 127503467L

let rr = System. Security.Cryptography.RandomNumberGenerator.Create()
Seq.init 1000000 (fun _ -> rr.GetBytes(arr); arr.[0] |> int64) |> Seq.sum

Real: 00:00:00.365, CPU: 00:00:00.359, GC gen0: 44, gen1: 0, gen2: 0
val it : int64 = 127460809L

这暗示了1:2的关系和加密版本的更好的记忆行为。

结论

System.Random主要是因为它具有更好的API,在某种程度上是由于其性能和良好的分发能力。System.Random还可以减少库依赖性,如果移植了框架,则System.Random可能会在Crypto变体之前可用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.