3

How can you generate a cryptographically safe integer that falls within a range in C#? I am trying to satisfy a Veracode issue reporting CWE-331: Insufficient Entropy. Obviously the code below is not cryptographically secure.

Random rand = new Random();
return rand.Next(999999);

I generally use the RNGCryptoServiceProvider class to generate cryptographically safe data, but not sure how to safely convert that to a number in a range.

Some posts state that you can use the modulo operator, but others report that it skews the distribution of random results.

Is the code below cryptographically safe?

byte[] data = new byte[8];
ulong value;
do
{
    rng.GetBytes(data);
    value = BitConverter.ToUInt64(data, 0);
} while(value==0);
result = (int)(value%15+1);
Hoppe
  • 143
  • 5
  • It depends on what properties this randomness has to fulfill. What are the constraints? Does it need to be unbiased? –  Aug 05 '19 at 14:11
  • The goal is to satisfy the Veracode report item securely. Specifically, it is generating an authentication nonce – Hoppe Aug 05 '19 at 14:16
  • Satisfying Veracode isn't the purpose of the random number. Being a nonce is. Luckily, the only things a nonce really has to fulfill is being unpredicable, so you can write some easy code. –  Aug 05 '19 at 14:19
  • Amusingly, your example _is_ actually unbiased. A random 64 bit unsigned integer %15+1 would generate a 1 with chance 1,229,782,938,247,303,442/18,446,744,073,709,551,616, and any of 2-15 with chance 1,229,782,938,247,303,441/18,446,744,073,709,551,616, but you're excluding `value==0`, so that's one less chance to generate a 1. Of course, it's unbiased purely based on luck. – AndrolGenhald Aug 05 '19 at 15:40
  • Related: https://security.stackexchange.com/a/39269, https://crypto.stackexchange.com/a/35263. tl;dr: `random % n` isn't a practical issue if `n` is a _really really small_ compared to the number of possible `random` values. Otherwise, use code like [this](https://security.stackexchange.com/a/214696/151903) to discard random values that would introduce bias. – AndrolGenhald Aug 05 '19 at 15:53
  • the part about modulo strong relates to a crypto question i asked a while back: https://crypto.stackexchange.com/questions/44788/one-time-pad-alphabet-size . It's all about how much you "throw away", and how much extra that causes repetition above and beyond expectations. – dandavis Aug 05 '19 at 21:42

2 Answers2

3

When you create random numbers, you need to ask yourself what those random numbers are for.

For example, if you wrote a first person shooter and wanted to generate some random bullet spread, then the randomness doesn't need to be of high entropy or uniformly distributed, as long as it's "good enough" for the game.

On the other hand, if you were to write code that serves some kind of cryptographic purpose, then the random numbers you use have to fulfill certain properties, depending on their exact purpose. Back in 2008, the Debian team had a huge bug in their OpenSSL package, causing the quality of the random numbers to decrease significantly. Ironically enough, this was all due to a code quality tool complaining.

What is a nonce?

You mentioned that you intend to use your random numbers as a nonce. It really depends on the exact purpose of the nonce, but a nonce should fulfill two properties:

  1. It should only be used once, hence the name "nonce".
  2. It should be impossible to predict.

The second property is really what your code is all about.

Why is System.Random not okay?

The System.Random class was designed to provide random-looking numbers quickly. These numbers are really not "high quality", and designed for something like a game, as I showed before.

An attacker, who is able to read enough random numbers generated by your system may be able to predict future numbers generated by the system. This may not be a problem in a game, but is very likely a problem if you look at your crypto code and imagine the attacker knowing all the nonces.

What to use instead?

The MSDN page for System.Random recommends using System.Security.Cryptography.RNGCryptoServiceProvider. As the name implies, it's designed to be used for cryptographic applications. Be aware if you are already using a third party crypto library, they may provide their own interface for generating secure random numbers, which may be more user-friendly and less prone to implementation errors.

Usually, when developers generate random numbers, they want them in a specific range, such as "Between 0 and 9999". This gives way to biasing errors, which, due to the way the modulo operator works, makes some values more likely than others.

But since all you care is unpredictability, you can make your life easy and generate a nonce in an easy-to-use size, which means just generating it byte-wise.

For example, the following code generates a nonce of the desired length:

private static RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();

public static byte[] GenerateNonce(uint nonceLength)
{
    byte[] nonce = new byte[nonceLength];

    rngCsp.GetBytes(nonce);

    return nonce;
}

Why is this code simpler than the others? Because the code you provided is designed to deal with the case I mentioned above, where biasing can be a problem. Since you can generate "nice" values, you will never have biasing issues and your code becomes much easier.

Is this nonce really secure? How long does it need to be?

Yes. Well, almost. An attacker can still guess the nonce and has a small chance of guessing it right. But it's the same as thinking of a number between 0 and 2^128-1 and not telling you what it is. You can still guess, but your chances of guessing it correctly are...small. Very small.

In fact, they become smaller the larger the nonce becomes. How long should a nonce be to make guessing infeasible? The general answer is it should have at least 80 bits of entropy, so 10 bytes at minimum. If you feel like you can deal with the overhead, you can go ahead and use 16 bytes (128 bits) and you should be good to go.

  • For ease of testing, I was hoping to swap out the existing code that returns an int, with a new one. Is there a way I can easily convert the byte array to a cryptographically safe int in the same range? – Hoppe Aug 05 '19 at 14:56
  • @Hoppe No, because integers are at maximum 64 bits wide, not 128. You can, however, use 32 bit wide nonces for testing. Although I wonder *what exactly* you would test. –  Aug 05 '19 at 15:15
  • Nonces in general don't have to be impossible to predict (CTR and GCM encryption modes both specify ways to use an incrementing nonce). In addition, "used once" is harder to satisfy when generating randomly due to the [birthday bound](https://en.wikipedia.org/wiki/Birthday_attack#Mathematics), 80 bits might be too low if there's a chance you might (or an attacker might make you) generate many nonces per second for an extended period. It depends a lot on the use case really. – AndrolGenhald Aug 05 '19 at 15:30
  • @AndrolGenhald True. It's difficult to give "generic" advice, especially for cryprographic applications. As soon as crypto becomes involved, the best advice is really "Consult an expert". –  Aug 05 '19 at 16:07
0

I believe something like this should work. You will need to modify it if you need negative values or 64 bit integers. Max output will be UInt32.MaxValue - 1. If you need the full range you better just do GetBytes and ToUInt32.

    /// <summary>
    /// Returns a random unsigned, 32-bit integer that is less than a specified limit.
    /// </summary>
    /// <param name="max">The exclusive max value of the result.</param>
    /// <returns></returns>
    public UInt32 GetRandomInt(UInt32 max = UInt32.MaxValue)
    {
        //might want to throw an exception or something instead
        if (max < 2)
            return 0;
        //everything above this will skew distribution
        UInt32 discardLimit = UInt32.MaxValue - (UInt32.MaxValue % max);
        UInt32 ret = 0;
        using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
        {
            byte[] retBuffer = new byte[4];
            bool validResult = false;
            //it is theoretically possible to loop forever here, maybe add a counter to throw an exception eventually
            while (!validResult)
            {
                rng.GetBytes(retBuffer);
                ret = BitConverter.ToUInt32(retBuffer, 0);
                if (ret <= discardLimit)
                {
                    validResult = true;
                    ret = ret % max;
                }
            }
        }
        return ret;
    }

It might be worthwhile to keep an instance of the random number generator and use that instead of creating and disposing it every time.

There are probably optimizations that can be done by saving the discarded bits and turning them into another attempt, or by using larger buffers to reduce the number of calls to the random number generator, but this works for infrequent number generation.

If you just need a nonce then see the other answer and just use GetBytes on a buffer.

user
  • 606
  • 7
  • 11