Last Updated on February 11, 2024


As a software developer, you will sometimes need to generate random numbers in your code. Computer programs generate random numbers by either leveraging the system hardware through the operating system or by calling a library exposing a Pseudo-Random Number Generator (PRNG).

Usually, random number generators that leverage the system hardware, will add enough entropy to the mix to ensure as much randomization as possible. Therefore, they are more suitable for cryptographic use cases. For that reason, they fall into the category of Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).

However, leveraging the system hardware is an operation that can be resource-intensive and most software programs don’t really need that level of randomization. Therefore, it’s more common to generate random numbers with pseudo-random number generators, especially if these numbers will be used in a non-cryptographic use case.

Pseudo-Random Number Generators are algorithms that generate a sequence of numbers in a deterministic way. These generators take some data in input and generate a sequence of numbers based on that input. Consequently, the sequence of numbers generated will be identical if you call the algorithm with the same input. That’s why these generators are also called deterministic random bit generators. They are not perfect, but they are good enough for some software development use cases.

In .NET, you have the option to generate random numbers by using either a PRNG or a CSPRNG. Let’s look at these options.

The Random class

The Random class is a PRNG that you can use to generate random numbers in a deterministic way.

Basically, a Random object will create a sequence of numbers based on the initial seed value. It’s important to note that the default constructor will create its own initial seed. In such a case, the algorithm used by Microsoft to create that initial seed depends on the .NET version that your code is using. For example, in the .NET framework, the default seed is derived from the system clock as stated in the documentation.

Here is a code sample that uses both constructors:

static void Main(string[] args)
{
    Console.WriteLine("Testing the Random class");
    var random1 = new Random();

    //random2 and random3 have the same seed
    var random2 = new Random(123456789);
    var random3 = new Random(123456789);

    Console.WriteLine("-------------------------------------------------------------------------------------");
    Console.WriteLine("Generating 3 integers and 3 doubles with random1. (random1 = new Random())");
    GenerateNumbers(random1);

    Console.WriteLine("-------------------------------------------------------------------------------------");
    Console.WriteLine("Generating 3 integers and 3 doubles with random2. (random2 = new Random(123456789))");
    GenerateNumbers(random2);

    Console.WriteLine("-------------------------------------------------------------------------------------");
    Console.WriteLine("Generating 3 integers and 3 doubles with random3. (random3 = new Random(123456789))");
    GenerateNumbers(random3);
}

private static void GenerateNumbers(Random random)
{
    for (int i = 0; i < 3; i++)
    {
        Console.WriteLine("{0:G}", random.Next());
        Console.WriteLine("{0:R}", random.NextDouble());
    }
}

Running this code will give the following output in the console:

Generate random numbers with the Random class

Therefore, two Random objects with the same seed value will generate the same sequence numbers. This behavior is good enough for most scenarios, except for cryptographic or security use cases. In other words, you should not use the Random class to perform cryptographic operations or generate nonce or secrets because once your seed value becomes public, the sequence is predictable.  

NB: Creating the Random class with your own initial seed may be very practical for unit tests. Basically, it guarantees that the same sequence of random numbers is used for every run of the unit tests.



The RNGCryptoServiceProvider class

The RNGCryptoServiceProvider allows developers to generate random numbers by using the cryptographic service provider (CSP). This relies entirely on the operating system, which ensures that the randomization is not deterministic. You can use this class if your target framework version is .NET 5.0 or lower. However, if you are using .NET 6.0 or higher, you will notice that this class is now obsolete. You need to switch to the RandomNumberGenerator class in this case.

static void Main(string[] args)
{
    Console.WriteLine("Trying the RNGCryptoServiceProvider class");
    using (RNGCryptoServiceProvider rngCrypto = new RNGCryptoServiceProvider())
    {
        Console.WriteLine("Generating 5 integers with rngCrypto = new RNGCryptoServiceProvider()");
        for (int i = 0; i < 5; i++)
        {
            byte[] randomUnsignedInteger32Bytes = new byte[4];
            rngCrypto.GetBytes(randomUnsignedInteger32Bytes);
            int randomInt32 = BitConverter.ToInt32(randomUnsignedInteger32Bytes, 0);
            Console.WriteLine("{0:G}", randomInt32);
        }
    }
}

Executing that code gives the following output:

Trying RNGCryptoServiceProvider class

The RandomNumberGenerator class

The RandomNumberGenerator class is an abstract class with a default implementation allowing to generate random numbers that can be used in cryptographic operations. This implementation also relies on the operating system to generate the numbers. You can create a new instance of a cryptographic random number generator by calling the static method: RandomNumberGenerator.Create().

Here is a code sample that uses the RandomNumberGenerator class:

static void Main(string[] args)
{
    Console.WriteLine("Trying the RandomNumberGenerator class");
    using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
    {
        Console.WriteLine("Generating 5 integers with rng = RandomNumberGenerator.Create()");
        for (int i = 0; i < 5; i++)
        {
            byte[] randomUnsignedInteger32Bytes = new byte[4];
            rng.GetBytes(randomUnsignedInteger32Bytes);
            int randomInt32 = BitConverter.ToInt32(randomUnsignedInteger32Bytes, 0);
            Console.WriteLine("{0:G}", randomInt32);
        }
    }

    Console.WriteLine("-------------------------------------------------------------------------------------");
    Console.WriteLine("Generating 5 integers with the static method RandomNumberGenerator.GetInt32");
    for (int i = 0; i < 5; i++)
    {
        int randomInt32 = RandomNumberGenerator.GetInt32(Int32.MinValue, Int32.MaxValue);
        Console.WriteLine("{0:G}", randomInt32);
    }
}

And the console output of that code sample:

Trying the RandomNumberGenerator class

Which one should you use?

In a nutshell, if you need to generate random numbers for cryptographic purposes, you should use the RandomNumberGenerator class instead of the RNGCryptoServiceProvider class because the latter class is obsolete in .NET 6.0. On the other hand, if you just need to generate random numbers for basic use cases (ie. not related to security or cryptographic), then you should use the Random class.