94

Why would anybody use the "standard" random number generator from System.Random at all instead of always using the cryptographically secure random number generator from System.Security.Cryptography.RandomNumberGenerator (or its subclasses because RandomNumberGenerator is abstract)?

Nate Lawson tells us in his Google Tech Talk presentation "Crypto Strikes Back" at minute 13:11 not to use the "standard" random number generators from Python, Java and C# and to instead use the cryptographically secure version.

I know the difference between the two versions of random number generators (see question 101337).

But what rationale is there to not always use the secure random number generator? Why use System.Random at all? Performance perhaps?

Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
Lernkurve
  • 20,203
  • 28
  • 86
  • 118

13 Answers13

159

Speed and intent. If you're generating a random number and have no need for security, why use a slow crypto function? You don't need security, so why make someone else think that the number may be used for something secure when it won't be?

Cole Tobin
  • 9,206
  • 15
  • 49
  • 74
Kevin LaBranche
  • 20,908
  • 5
  • 52
  • 76
  • 33
    I very much like the intent argument. – Lernkurve Aug 10 '09 at 21:35
  • it all about entropy generation and speed – Sergey Mirvoda Aug 11 '09 at 06:12
  • 12
    It should be noted that Random.GetNext is far from good at "spreading" the random numbers over the spectrum, especially in a threaded environment. I ran across this problem when writing a program to test different solutions to the Rand7 from Rand5 problem. In a quick threaded test just now of 100000 random numbers between 0 and 10, 82470 of the generated numbers were 0. I saw similar discrepancies in my previous tests. Crytpography random is very even in its distribution of numbers. I guess the lesson is to always test your random data to see that it is "random enough" for your needs. – Kristoffer L May 31 '11 at 12:14
  • 36
    @Kristoffer I think you misused `Random`. Let me guess: You created a new instance of the `Random` class for each number, which since it's seeded by a coarse timer will be seeded with the same value for an interval of about 1-16ms. – CodesInChaos Jun 17 '11 at 10:33
  • @CodeInChaos Not at all. I was merely running the test in a multi-threaded environment. It does not always screw up the resuls, but the outcome is quite unpredictable (in a bad way, even if we are talking about random. :P) – Kristoffer L Jan 10 '12 at 16:18
  • 15
    @CodesInChaos: Besides that, there is a race-condition with `Random` that causes it to return all 0's when the same object is used from multiple threads. – BlueRaja - Danny Pflughoeft Oct 25 '12 at 22:25
  • 3
    @KristofferL: See above comment, also see [this answer](http://stackoverflow.com/questions/3049467/is-c-sharp-random-number-generator-thread-safe/11109361#11109361) – BlueRaja - Danny Pflughoeft Oct 25 '12 at 22:25
66

Apart from the speed and the more useful interface (NextDouble() etc) it is also possible to make a repeatable random sequence by using a fixed seed value. That is quite useful, amongst others during Testing.

Random gen1 = new Random();     // auto seeded by the clock
Random gen2 = new Random(0);    // Next(10) always yields 7,8,7,5,2,....
Afzaal Ahmad Zeeshan
  • 15,669
  • 12
  • 55
  • 103
H H
  • 263,252
  • 30
  • 330
  • 514
  • 2
    And there's the BitConverter.ToInt32(Byte[] value, int startIndex) which may be easier to understand. ;) – sisve Oct 25 '09 at 09:17
  • 7
    Ian Bell and David Braben used a random generator in the computer game Elite to create a vast list of planets and their attributes (size, etc), with very limited memory. This also relies on the generator creating a deterministic pattern (from a seed) - which the Crypto one obviously doesn't provide (by design.) There's some more information on how they did it here: http://wiki.alioth.net/index.php/Random_number_generator and the book "Infinite Game Universe: Mathematical Techniques" ISBN:1584500581 has a more general discussion on such techniques. – Daniel James Bryars Sep 26 '10 at 12:34
  • 7
    Do keep in mind that MSDN does not guarantee this property to hold across .NET versions: _["The implementation of the random number generator in the Random class is not guaranteed to remain the same across major versions of the .NET Framework."](http://msdn.microsoft.com/en-us/library/system.random%28v=vs.110%29.aspx)_ – Roman Starkov Jan 25 '14 at 19:01
  • @romkyns MSDN *does* guarantee that the property will hold across .NET versions. The implementation may change, but the behavior of the class when constructed with the parameterized constructor is part of its contract, not its implementation (see http://msdn.microsoft.com/en-us/library/ctssatww(v=vs.110).aspx). – phoog Apr 02 '14 at 20:28
  • 2
    @phoog _"As a result, your application code should not assume that the same seed will result in the same pseudo-random sequence in different versions of the .NET Framework."_ - I dunno, seems pretty clear to me. However, I wouldn't be surprised if they can't change it in practice without breaking existing programs, despite this warning. – Roman Starkov Apr 02 '14 at 20:31
  • @romkyns Yes, the specific repeatable sequence may change if you run your code on a different version of the framework. So, for example, a test might pass on one version of the framework and not another, but the behavior *will* be consistent within the context of a given framework. I understood your comment to mean that a future version of the framework might have an implementation where the same seed could give different sequences, and that isn't true -- or if it were, it would be a bug. – phoog Apr 08 '14 at 19:04
  • 2
    @phoog: You’re saying one thing and then the exact opposite of it. You’re contradicting yourself directly. – Timwi Apr 09 '14 at 12:06
  • 2
    @Timwi what two things did I say that contradicted each other? I said that the behavior of identically-seeded Random objects must be the same according to the class's contract. I said this because I (mis-?) understood romkyn's comment to say that future implementations of Random might be such that identically-seeded objects could return different sequences. I also acknowledged that changing framework versions might cause a one-time change in the specific repeatable sequence returned. That's not the same as losing the property of repeatability altogether. – phoog Apr 29 '14 at 21:33
59

First of all the presentation you linked only talks about random numbers for security purposes. So it doesn't claim Random is bad for non security purposes.

But I do claim it is. The .net 4 implementation of Random is flawed in several ways. I recommend only using it if you don't care about the quality of your random numbers. I recommend using better third party implementations.

Flaw 1: The seeding

The default constructor seeds with the current time. Thus all instances of Random created with the default constructor within a short time-frame (ca. 10ms) return the same sequence. This is documented and "by-design". This is particularly annoying if you want to multi-thread your code, since you can't simply create an instance of Random at the beginning of each thread's execution.

The workaround is to be extra careful when using the default constructor and manually seed when necessary.

Another problem here is that the seed space is rather small (31 bits). So if you generate 50k instances of Random with perfectly random seeds you will probably get one sequence of random numbers twice (due to the birthday paradox). So manual seeding isn't easy to get right either.

Flaw 2: The distribution of random numbers returned by Next(int maxValue) is biased

There are parameters for which Next(int maxValue) is clearly not uniform. For example if you calculate r.Next(1431655765) % 2 you will get 0 in about 2/3 of the samples. (Sample code at the end of the answer.)

Flaw 3: The NextBytes() method is inefficient.

The per byte cost of NextBytes() is about as big as the cost to generate a full integer sample with Next(). From this I suspect that they indeed create one sample per byte.

A better implementation using 3 bytes out of each sample would speed NextBytes() up by almost a factor 3.

Thanks to this flaw Random.NextBytes() is only about 25% faster than System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes on my machine (Win7, Core i3 2600MHz).

I'm sure if somebody inspected the source/decompiled byte code they'd find even more flaws than I found with my black box analysis.


Code samples

r.Next(0x55555555) % 2 is strongly biased:

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

Performance:

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);
}
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • 1
    Interesting, can confirm your finding: on my machine Next(1431655765) also gives 2/3 with any seeding. What is the magic of 1431655765? How did you arrive at this number? – citykid Mar 02 '14 at 18:06
  • 1
    @citykid Look at the number as hex or bits. It's magic arises from the dubious way `Random` uses to transform a 31 bit integer into a number with the specified upper bound. I forgot the details, but it's something like `randomValue * max / 2^{31}`. – CodesInChaos Mar 03 '14 at 09:00
  • 1431655765_10 = 1010101010101010101010101010101_2 – Tim S. Mar 28 '14 at 17:38
  • 7
    Hm. So what implementation of Random for C# do you recommend using? – Arsen Zahray Jul 15 '14 at 15:05
  • 1
    Holy cow, the non-uniformity of the distribution of `Next()`, demonstrated by you here, is a pretty spectacular bug - and still present today, 6 years after you first wrote up your findings. (I say "bug" rather than merely "flaw", because [the docs](https://msdn.microsoft.com/en-us/library/system.random(v=vs.110).aspx) claim that *"Pseudo-random numbers are chosen with equal probability from a finite set of numbers"*. It ain't so, and your code here proves it.) – Mark Amery Aug 29 '17 at 20:21
  • I put a recommendation of a random number generator in my answer. The one from JPL written in 1984 hasn't been beat by anything I've seen (for a repeatable pseudo-random sequence that you don't use for cryptographic apps.) . You'd have to port it to C#, but that didn't take me long to do. The period is really long and one bit different in your seed results in a totally different sequence of numbers. – Traderhut Games Dec 21 '18 at 21:59
  • I'm sure if somebody inspected the source/decompiled byte code >> Here you go. Reference [source code of System.Random](https://referencesource.microsoft.com/#mscorlib/system/random.cs). – Sen Jacob Oct 10 '19 at 12:31
26

System.Random is much more performant since it does not generate cryptographically secure random numbers.

A simple test on my machine filling a buffer of 4 bytes with random data 1,000,000 times takes 49 ms for Random, but 2845 ms for RNGCryptoServiceProvider. Note that if you increase the size of the buffer you are filling, the difference narrows as the overhead for RNGCryptoServiceProvider is less relevant.

Michael
  • 54,279
  • 5
  • 125
  • 144
  • 2
    Thank you for demonstrating it with an actual test. – Lernkurve Aug 10 '09 at 21:50
  • 5
    You might think this is harsh, but -1 for posting results of a performance benchmark without including the code of the benchmark. Even if the performance characteristics of `Random` and `RNGCryptoServiceProvider` haven't changed in the last 8 years (which for all I know they might've), I've seen enough completely broken benchmarks used on Stack Overflow not to trust the results of a benchmark whose code isn't publicly available. – Mark Amery Aug 29 '17 at 20:28
  • Just to add to this old comment thread: System.Random is not really performant. There are PRNG implementations that are _both_ more performant _and_ statistically better than System.Random. For example, my XoshiroPRNG.Net implementation is both; it's available on https://www.nuget.org/packages/XoshiroPRNG.Net/ -- follow the link to the repo to see the performance benchmark code & results. – pepoluan Jun 08 '21 at 05:07
22

The most obvious reasons have already been mentioned, so here's a more obscure one: cryptographic PRNGs typically need to be continually be reseeded with "real" entropy. Thus, if you use a CPRNG too often, you could deplete the system's entropy pool, which (depending on the implementation of the CPRNG) will either weaken it (thus allowing an attacker to predict it) or it will block while trying to fill up its entropy pool (thus becoming an attack vector for a DoS attack).

Either way, your application has now become an attack vector for other, totally unrelated applications which – unlike yours – actually vitally depend on the cryptographic properties of the CPRNG.

This is an actual real world problem, BTW, that has been observed on headless servers (which naturally have rather small entropy pools because they lack entropy sources such as mouse and keyboard input) running Linux, where applications incorrectly use the /dev/random kernel CPRNG for all sorts of random numbers, whereas the correct behavior would be to read a small seed value from /dev/urandom and use that to seed their own PRNG.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • I read the Wikipedia article and some other Internet sources on entropy and entropy depletion, and I don't quite understand it. How can I be depleting the entropy pool when the random number generator is fed with system time, number of free bytes etc.? How can others use it as a attack vector to predict random numbers? Can you provide an easy example? Perhaps this discussion must be taken offline. http://en.wikipedia.org/wiki/Entropy_%28computing%29 – Lernkurve Aug 11 '09 at 09:27
  • 3
    System time is not an entropy source, because it's predictable. I'm not sure about the number of free bytes, but I doubt it's a high-quality entropy source either. By sending more requests to the server, the attacker can cause the number of free bytes to decrease, making it partially deterministic. You application becomes an attack vector because by depleting the entropy pool, it forces the other, security-critical application to use less-random random numbers -- or wait until the entropy source is replenished. – quant_dev Oct 25 '09 at 09:05
  • I understand that if one has a pseudo-random generator fed with e.g. a 32-bit seed, a brute-force attack will often be fairly easy; even a 64-bit seed may be subject to birthday attacks. Once the seed gets much larger than that, though, I don't quite see the risk. If one has a random generator which for each byte of output takes passes a 128-bit state through a block encryption algorithm and then outputs the bottom 8 bits, how could an attacker even with gigs of consecutive output bytes infer the state, absent weaknesses in the encryption algorithm itself? – supercat Nov 15 '13 at 19:14
11

If you're programming an online card game or lotter then you would want to make sure the sequence is next to impossible to guess. However, if you are showing users, say, a quote of the day the performance is more important than security.

Dan Diplo
  • 25,076
  • 4
  • 67
  • 89
9

This has been discussed at some length, but ultimately, the issue of performance is a secondary consideration when selecting a RNG. There are a vast array of RNGs out there, and the canned Lehmer LCG that most system RNGs consists of is not the best nor even necessarily the fastest. On old, slow systems it was an excellent compromise. That compromise is seldom ever really relevant these days. The thing persists into present day systems primarily because A) the thing is already built, and there is no real reason to 'reinvent the wheel' in this case, and B) for what the vast bulk of people will be using it for, it's 'good enough'.

Ultimately, the selection of an RNG comes down to Risk/Reward ratio. In some applications, for example a video game, there is no risk whatsoever. A Lehmer RNG is more than adequate, and is small, concise, fast, well-understood, and 'in the box'.

If the application is, for example, an on-line poker game or lottery where there are actual prizes involved and real money comes into play at some point in the equation, the 'in the box' Lehmer is no longer adequate. In a 32-bit version, it only has 2^32 possible valid states before it begins to cycle at best. These days, that's an open door to a brute force attack. In a case like this, the developer will want to go to something like a Very Long Period RNG of some species, and probably seed it from a cryptographically strong provider. This gives a good compromise between speed and security. In such a case, the person will be out looking for something like the Mersenne Twister, or a Multiple Recursive Generator of some kind.

If the application is something like communicating large quantities of financial information over a network, now there is a huge risk, and it heavily outweights any possible reward. There are still armored cars because sometimes heavily armed men is the only security that's adequate, and trust me, if a brigade of special ops people with tanks, fighters, and helicopters was financially feasible, it would be the method of choice. In a case like this, using a cryptographically strong RNG makes sense, because whatever level of security you can get, it's not as much as you want. So you'll take as much as you can find, and the cost is a very, very remote second-place issue, either in time or money. And if that means that every random sequence takes 3 seconds to generate on a very powerful computer, you're going to wait the 3 seconds, because in the scheme of things, that is a trivial cost.

  • 4
    I think you are wrong about your magnitudes; sending financial data needs to be extremely quick; if your trading algorithm can get to the result 0.1ms quicker than the competition, you end up better in the queue of buy/sell/stop-loss/quote commands. 3 seconds is an eternity. This is why traders invest in insanely good computers. See the previous answer; Crypt.RNG takes only 0,0028 ms per new number; 0.0000028 seconds, so you are off by 9 orders of magnitude in terms of how much processing it takes, and also on how important speed is. – Henrik Jan 21 '10 at 01:25
9

Note that the System.Random class in C# is coded incorrectly, so should be avoided.

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

evolvedmicrobe
  • 2,672
  • 2
  • 22
  • 30
4

Not everyone needs cryptographically secure random numbers, and they might benefit more from a speedier plain prng. Perhaps more importantly is that you can control the sequence for System.Random numbers.

In a simulation utilizing random numbers you might want to recreate, you rerun the simulation with the same seed. It can be handy for tracking bugs when you want to regenerate a given faulty scenario as well - running your program with the exact same sequence of random numbers that crashed the program.

nos
  • 223,662
  • 58
  • 417
  • 506
3

Random is not a random number generator, it is a deterministic pseudo-random sequence generator, which takes its name for historical reasons.

The reason to use System.Random is if you want these properties, namely a deterministic sequence, which is guaranteed to produce the same sequence of results when initialized with the same seed.

If you want to improve the "randomness" without sacrificing the interface, you can inherit from System.Random overriding several methods.

Why would you want a deterministic sequence

One reason to have a deterministic sequence rather than true randomness is because it is repeatable.

For example, if you are running a numerical simulation, you can initialize the sequence with a (true) random number, and record what number was used.

Then, if you wish to repeat the exact same simulation, e.g. for debugging purposes, you can do so by instead initializing the sequence with the recorded value.

Why would you want this particular, not very good, sequence

The only reason I can think of would be for backwards compatibility with existing code which uses this class.

In short, if you want to improve the sequence without changing the rest of your code, go ahead.

Ben
  • 34,935
  • 6
  • 74
  • 113
2

If I don't need the security, i.e., I just want a relatively indeterminate value not one that's cryptographically strong, Random has a much easier interface to use.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
2

Different needs call for different RNGs. For crypto, you want your random numbers to be as random as possible. For Monte Carlo simulations, you want them to fill the space evenly and to be able to start the RNG from a known state.

quant_dev
  • 6,181
  • 1
  • 34
  • 57
2

I wrote a game (Crystal Sliders on the iPhone: Here) that would put up a "random" series of gems (images) on the map and you would rotate the map how you wanted it and select them and they went away. - Similar to Bejeweled. I was using Random(), and it was seeded with the number of 100ns ticks since the phone booted, a pretty random seed.

I found it amazing that it would generate games that were nearly identical to each other -of the 90 or so gems, of 2 colors, I would get two EXACTLY the same except for 1 to 3 gems! If you flip 90 coins and get the same pattern except for 1-3 flips, that is VERY unlikely! I have several screen shots that show them the same. I was shocked at how bad System.Random() was! I assumed, that I MUST have written something horribly wrong in my code and was using it wrong. I was wrong though, it was the generator.

As an experiment - and a final solution, I went back to the random number generator that I've been using since 1985 or so - which is VASTLY better. It is faster, has a period of 1.3 * 10^154 (2^521) before it repeats. The original algorithm was seeded with a 16 bit number, but I changed that to a 32 bit number, and improved the initial seeding.

The original one is here:

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

Over the years, I've thrown every random number test I could think of at this, and it past all of them. I don't expect that it is of any value as a cryptographic one, but it returns a number as fast as "return *p++;" until it runs out of the 521 bits, and then it runs a quick process over the bits to create new random ones.

I created a C# wrapper - called it JPLRandom() implemented the same interface as Random() and changed all the places where I called it in the code.

The difference was VASTLY better - OMG I was amazed - there should be no way I could tell from just looking at the screens of 90 or so gems in a pattern, but I did an emergency release of my game following this.

And I would never use System.Random() for anything ever again. I'm SHOCKED that their version is blown away by something that is now 30 years old!

-Traderhut Games

Traderhut Games
  • 1,222
  • 1
  • 15
  • 30
  • 4
    My first guess is that you recreated `Random` too often. It should only be created once calling `Next` on that instance many times. `Random` is bad, but not *that* bad. Can you post a sample program along with a pair of seeds that exhibits this problem? – CodesInChaos Mar 28 '14 at 09:43
  • The code would create a Random() at the beginning of each level (but it was a major problem with level 1 more than later ones) The code was roughly as follows: – Traderhut Games Mar 29 '14 at 06:16
  • Rnd = new Random ((uint)GameSeed); NextGameSeed = Rnd.Next (2000000000); Each level used a new Random that was created with a new seed - The Seed was saved for each level so I would be able to recreate the map, and also confirm the sequence of random seeds matched. This allows me to confirm that the game is a valid series of maps that have been solved and recreate the game. – Traderhut Games Mar 29 '14 at 06:25
  • And Initially, Random was created based on System.DateTime.Now.Ticks (or 0), and then the GameSeed was picked using the same call as the Rnd.Next() above. If I can't do this, then there is a serious issue with the seeding of the random number generator. – Traderhut Games Mar 29 '14 at 06:30
  • this is not an answer to the original question! – Mike Dinescu Mar 03 '15 at 18:52
  • It is, the answer is: There are no situations where calling Random() makes sense. Use the Crypto if you need random numbers for Cryptography or other situations that require highly random events. If you want a random sequence of numbers for a game or something - use some other algorithm - such as the one that I've suggested, do not call Random() as it is amazingly bad. (And can make your game seem dorky like it did mine.) – Traderhut Games Mar 03 '15 at 19:01
  • @TraderhutGames wait, you generated your next seed to use from your previously-seeded `Random()` (specifically, by calling `.Next(2000000000)`) at the moment of creation, and were then surprised to get identical games happening in sequence? That's almost certainly going to happen if you use your seeding approach, regardless of the quality of your deterministic pseudo-random number generation algorithm; eventually, you're likely to hit some seed `x` such that `new Random(x).Next(2000000000) == x`, and then you're stuck with identical sequences forever. – Mark Amery Aug 29 '17 at 20:53
  • The intent is that for the first Random() call it would generate a random series of numbers, so the first thing it did was generate the seed for the next level. The next level would seed with that seed. This gave me the ability to reproduce the exact same series of numbers, but that each game would be totally different. The problem was that seeding with a number from 0-2 billion would produce about the same set of numbers each time. The seed didn't seem to make all that much difference! I should have had a reproducible, but random series, but didn't. – Traderhut Games Sep 15 '17 at 19:26
  • btw Mark: You are correct that you would have one chance in 2,000,000,000 that that seed would return the same seed that it was fed with, If someone plays 2 billion games and finds that seed, they win the game. Feel free to play it until you find it. And the next time I'll add code so if rnd(2000000000) = old_seed, then pick the next one. – Traderhut Games Nov 16 '20 at 20:57