Saturday, April 6, 2013

Cryptographically Secure Pseudo-Random Number Generation in .NET

Many times we need to generate random variables for numerous reasons. When a trivial value is required, the Random class can be just fine but when you need a value that will be used to keep something unique and secure, the Random class simply will not do. This is where the RNGCryptoServiceProvider class comes in to play.

Most would be quick to simply pump out whatever value they need from this class and assume all is well, but beware, as this may not be the case.

If the value will be used to keep something secure, it needs to be cryptographically secure and while that is the purpose of the RNGCryptoServiceProvider, it is still up to you to make sure that the value is even capable of being secure.

Entropy is a term used to describe the randomness of a value. According to current guidelines, a value must contain at least 64 bits of entropy to be considered cryptographically secure. That means, it must contain 64 truly random bits, at a minimum. The reasoning of this is that with current computing power, anything less that 64 random bits would be a relatively trivial task for a machine to guess by sequential generation of values. I'm not saying this can't be done easily with a very powerful system or, as many "hackers" are doing these days, pushing the work on to a very powerful GPU, but we should be safe to assume that the entry point for secure random values will start at 64 bit of entropy.

Lets take a look at how this can be done in a re-usable class, by creating an example. We'll call it CSPRNG (Cryptographically Secure Pseudo Random Number Generator).

    public static class CSPRNG
    {
        public static byte[] GetBytes(int entropy)
        {
            if (entropy < 8)
            {
                throw new ArgumentOutOfRangeException("entropy",
                    "Entropy must be 64 bits or greater to be cryptographically secure.");
            }
            using (RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider())
            {
                byte[] _obj = new byte[entropy];
                _rng.GetBytes(_obj);
                return _obj;
            }
        }

        public static Guid GetGuid()
        {
            using (RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider())
            {
                byte[] _obj = new byte[16];
                _rng.GetBytes(_obj);
                return new Guid(_obj);
            }
        }

        public static long GetInt64(bool allowNegativeValue = false)
        {
            using (RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider())
            {
                byte[] _obj = new byte[8];
                _rng.GetBytes(_obj);
                long _num = BitConverter.ToInt64(_obj, 0);
                if (!allowNegativeValue)
                {
                    _num = (_num & ~(1L << 63));
                }
                return _num;
            }
        }
    }

This static class is capable of creating 3 different types of variables for use: a byte array with a minimum length of 8 bytes(64 bits), a Guid(128 bits) and an Int64 value(64 bits).

You will notice that the above class does NOT allow any value less than 64 bits (or 8 bytes) to be generated. If it is requested to do so, it will throw an error. This is so we can be assured that this class will ONLY create cryptographically secure values.

The Int64 generator features an optional parameter that controls if negative values are allowed or not. It does so by guaranteeing the most significant bit is ALWAYS turned-off when non-negative values are not allowed. Technically, this could be an issue. A signed Int64 value uses the most significant bit to indicate the signage of the value (positive or negative). By forcing positive values, we are making this value only 63 bits of entropy, but I allow this 1 exception to break the rule for the simple reason that sometimes a negative value will break the requirements of the caller. Another option would be to create an UInt64 (unsigned 64 bit integer) to ensure that all 64 bits are used for entropy.

Some may ask why a Guid would be included, as we could just simply create a seemingly random Guid from the classes static NewGuid() constructor. The reason is this: the static Guid constructor guarantees uniqueness but it does not guarantee a cryptographically secure value because it is not truly random. I've seen many posts on the web about how it would take trillions of years to create a collision and I assure you, they need to check their math, as that is not the case. On a sub-standard system, a duplicate value could be created in ~200 years. Yes that's a lot, but put that same algorithm on to a machine designed for brute force attacks and that number drops drastically.

Hope this sheds some light on keeping random values secure and feel free to use or modify the code above as you see fit.

No comments :

Post a Comment