RNGCryptoServiceProvider - Random Number Review

2019-01-17 20:24发布

问题:

While looking for best attempts at generating truly random numbers, I stumbled upon this code example.

Looking for opinions on this snippet.

using System;
using System.Security.Cryptography;

private static int NextInt(int min, int max)
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] buffer = new byte[4];

    rng.GetBytes(buffer);
    int result = BitConverter.ToInt32(buffer, 0);

    return new Random(result).Next(min, max);
}

Source: http://www.vcskicks.com/code-snippet/rng-int.php

Would this be preferred over using a tick count seed such as:

Random rand = new Random(Environment.TickCount); 
rand.Next(min, max);

Note:

I am not looking for third party random data providers such as Random.org, as such a dependency is not realistic to the application.

回答1:

Well, using RNGCryptoServiceProvider gives you an unguessable crypto-strength seed whereas Environment.TickCount is, in theory, predictable.

Another crucial difference would be evident when calling your NextInt method several times in quick succession. Using RNGCryptoServiceProvider will seed the Random object with a different crypto-strength number each time, meaning that it will go on to return a different random number for each call. Using TickCount risks seeding the Random object with the same number each time (if the method is called several times during the same "tick"), meaning that it will go on to return the same (supposedly random) number for each call.

If you genuinely need truly random numbers then you shouldn't be using a computer to generate them at all: you should be measuring radioactive decay or something similarly, genuinely unpredictable.



回答2:

I asked a similar question 2 years back :) check and see if it helps you. I used that code for generating a secure random number for payment processing.



回答3:

Don't use your code. Your solution is wrong and generates poor random numbers. I suggest my solution, which generates cryptographically strong random numbers:

public class SecureRandom : RandomNumberGenerator
{
    private readonly RandomNumberGenerator rng = new RNGCryptoServiceProvider();


    public int Next()
    {
        var data = new byte[sizeof(int)];
        rng.GetBytes(data);
        return BitConverter.ToInt32(data, 0) & (int.MaxValue - 1);
    }

    public int Next(int maxValue)
    {
        return Next(0, maxValue);
    }

    public int Next(int minValue, int maxValue)
    {
        if (minValue > maxValue)
        {
            throw new ArgumentOutOfRangeException();
        }
        return (int)Math.Floor((minValue + ((double)maxValue - minValue) * NextDouble()));
    }

    public double NextDouble()
    {
        var data = new byte[sizeof(uint)];
        rng.GetBytes(data);
        var randUint = BitConverter.ToUInt32(data, 0);
        return randUint / (uint.MaxValue + 1.0);
    }

    public override void GetBytes(byte[] data)
    {
        rng.GetBytes(data);
    }

    public override void GetNonZeroBytes(byte[] data)
    {
        rng.GetNonZeroBytes(data);
    }
}


回答4:

I realy do not suggest using provided example. Although RNGCryptoServiceProvider returns truly good random (or at least it should), but same is not true for Random. More over - it is not known if Random(value) creates true bijection against value retuned by Next(..). More over - it is not guaranteed that Next(min, max) returns value in truly random maner (meaning equal chances for number to hit each value).

I would first tear down problem to getting number in interval 0 - max (exclusive). Then I would use nearest power of 2 to get random value in range 0 - (2^n - 1). Now one thing you MUST never do here is using modulo to get number in prefered range like rand(0 - (2^n - 1)) % max, because by doing so you are actualy increasing chances of geting number in lower range.

Example - max = 3, n = 2 (0 - (2^2 - 1)) % 2, numbers (0, 1, 2, 3), corresponding values after modulo (0, 1, 2, 0). See that we hit 0 twice which is realy bad random.

So solution would be to use crypto random to get value to nearest power of two and in case if value is outside maximum range, repeat proceudre (get another crypto random) until value is inside given range. This would be much better algorythm.



回答5:

I think this is a more efficient, and possibly faster generator then the ones listed above..

public static class SecureRandom
{
    #region Constants
    private const int INT_SIZE = 4;
    private const int INT64_SIZE = 8;
    #endregion

    #region Fields
    private static RandomNumberGenerator _Random;
    #endregion

    #region Constructor
    static SecureRandom()
    {
        _Random = new RNGCryptoServiceProvider();
    }
    #endregion

    #region Random Int32
    /// <summary>
    /// Get the next random integer
    /// </summary>
    /// <returns>Random [Int32]</returns>
    public static Int32 Next()
    {
        byte[] data = new byte[INT_SIZE];
        Int32[] result = new Int32[1];

        _Random.GetBytes(data);
        Buffer.BlockCopy(data, 0, result, 0, INT_SIZE);

        return result[0];
    }

    /// <summary>
    /// Get the next random integer to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [Int32]</returns>
    public static Int32 Next(Int32 MaxValue)
    {
        Int32 result = 0;

        do
        {
            result = Next();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random UInt32
    /// <summary>
    /// Get the next random unsigned integer
    /// </summary>
    /// <returns>Random [UInt32]</returns>
    public static UInt32 NextUInt()
    {
        byte[] data = new byte[INT_SIZE];
        Int32[] result = new Int32[1];

        do
        {
            _Random.GetBytes(data);
            Buffer.BlockCopy(data, 0, result, 0, INT_SIZE);
        } while (result[0] < 0);

        return (UInt32)result[0];
    }

    /// <summary>
    /// Get the next random unsigned integer to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [UInt32]</returns>
    public static UInt32 NextUInt(UInt32 MaxValue)
    {
        UInt32 result = 0;

        do
        {
            result = NextUInt();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random Int64
    /// <summary>
    /// Get the next random integer
    /// </summary>
    /// <returns>Random [Int32]</returns>
    public static Int64 NextLong()
    {
        byte[] data = new byte[INT64_SIZE];
        Int64[] result = new Int64[1];

        _Random.GetBytes(data);
        Buffer.BlockCopy(data, 0, result, 0, INT64_SIZE);

        return result[0];
    }

    /// <summary>
    /// Get the next random unsigned long to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [UInt64]</returns>
    public static Int64 NextLong(Int64 MaxValue)
    {
        Int64 result = 0;

        do
        {
            result = NextLong();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random UInt32
    /// <summary>
    /// Get the next random unsigned long
    /// </summary>
    /// <returns>Random [UInt64]</returns>
    public static UInt64 NextULong()
    {
        byte[] data = new byte[INT64_SIZE];
        Int64[] result = new Int64[1];

        do
        {
            _Random.GetBytes(data);
            Buffer.BlockCopy(data, 0, result, 0, INT64_SIZE);
        } while (result[0] < 0);

        return (UInt64)result[0];
    }

    /// <summary>
    /// Get the next random unsigned long to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [UInt64]</returns>
    public static UInt64 NextULong(UInt64 MaxValue)
    {
        UInt64 result = 0;

        do
        {
            result = NextULong();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random Bytes
    /// <summary>
    /// Get random bytes
    /// </summary>
    /// <param name="data">Random [byte array]</param>
    public static byte[] NextBytes(long Size)
    {
        byte[] data = new byte[Size];
        _Random.GetBytes(data);
        return data;
    }
    #endregion
}


回答6:

It really depends on the intended use or requirement of the random number being generated.

The Random class is useful for practical randomization like randomizing the order images display in an image rotator or rolls of a die.
If, on the other hand, you need random numbers requiring a greater amount of security, like to generate a password or payment confirmation key, then using a class such as RNGCryptoServiceProvider or creating your own implementation of the abstract class RandomNumberGenerator that implements a cryptographic algorithm are better alternatives.



回答7:

Ok, so I'm a little late to the party, but I really wanted a full implementation of System.Random that can be called multiple times during the same timer tic and yield different results. After much agonizing over different implementations, I settled on the simplest one that I came up with, which provides a default constructor that supplies a random key to the base System.Random constructor:

/// <summary> An implementation of System.Random whose default constructor uses a random seed value rather than the system time. </summary>
public class RandomEx : Random
{
    /// <summary> Initializes a new CryptoRandom instance using a random seed value. </summary>
    public RandomEx()
        : base(_GetSeed())
    { }

    /// <summary> Initializes a new CryptoRandom instance using the specified seed value. </summary>
    /// <param name="seed"> The seed value. </param>
    public RandomEx(int seed)
        : base(seed)
    { }

    // The static (shared by all callers!) RandomNumberGenerator instance
    private static RandomNumberGenerator _rng = null;

    /// <summary> Static method that returns a random integer. </summary>
    private static int _GetSeed()
    {
        var seed = new byte[sizeof(int)];

        lock (typeof(RandomEx)) {
            // Initialize the RandomNumberGenerator instance if necessary
            if (_rng == null) _rng = new RNGCryptoServiceProvider();

            // Get the random bytes
            _rng.GetBytes(seed);
        }

        // Convert the bytes to an int
        return BitConverter.ToInt32(seed, 0);
    }
}

Along the way, I also wrote and tested an implementation that overrides the methods necessary to use RNGCryptoServiceProvider to provide ALL of the random values (rather than relying on whatever random number generator is baked into the System.Random class). But I have no idea how cryptographically strong the results are by the time you take my random Sample() values and push 'em through the transformations to produce integer values. Anyway, here is the code if anyone wants it:

/// <summary> An implementation of System.Random that uses RNGCryptoServiceProvider to provide random values. </summary>
public class CryptoRandom : Random, IDisposable
{
    // Class data
    RandomNumberGenerator _csp = new RNGCryptoServiceProvider();

    /// <summary> Returns a random number between 0.0 (inclusive) and 1.0 (exclusive). </summary>
    protected override double Sample()
    {
        // Get a nonnegative random Int64
        byte[] bytes = new byte[sizeof(long)];
        _csp.GetBytes(bytes);
        long value = BitConverter.ToInt64(bytes, 0) & long.MaxValue;

        // Scale it to 0->1
        return (double)value / (((double)Int64.MaxValue) + 1025.0d);
    }

    /// <summary> Fills the elements of the specified array of bytes with random numbers. </summary>
    /// <param name="buffer"> An array of bytes to contain random numbers. </param>
    public override void NextBytes(byte[] buffer)
    {
        _csp.GetBytes(buffer);
    }

    /// <summary> Returns a nonnegative random integer. </summary>
    /// <returns> A 32-bit signed integer greater than or equal to zero. </returns>
    public override int Next()
    {
        byte[] data = new byte[4];
        _csp.GetBytes(data);
        data[3] &= 0x7f;
        return BitConverter.ToInt32(data, 0);
    }

    /// <summary> Returns a random integer that is within a specified range. </summary>
    /// <param name="minValue"> The inclusive lower bound of the random number returned. </param>
    /// <param name="maxValue"> The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue. </param>
    /// <returns> A 32-bit signed integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned. </returns>
    public override int Next(int minValue, int maxValue)
    {
        // Special case
        if (minValue == maxValue) return minValue;

        double sample = Sample();
        double range = (double)maxValue - (double)minValue;
        return (int)((sample * (double)range) + (double)minValue);
    }

    #region IDisposible implementation

    /// <summary> Disposes the CryptoRandom instance and all of its allocated resources. </summary>
    public void Dispose()
    {
        // Do the actual work
        Dispose(true);

        // This object will be cleaned up by the Dispose method. Call GC.SupressFinalize to 
        // take this object off the finalization queue and prevent finalization code for this object 
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios:
    //
    // If disposing is true, the method has been called directly or indirectly by a user's code and both
    // managed and unmanaged resources can be disposed. 
    //
    // If disposing is false, the method has been called by the runtime from inside the finalizer.
    // In this case, only unmanaged resources can be disposed. 
    protected virtual void Dispose(bool disposing)
    {
        if (disposing) {
            // The method has been called directly or indirectly by a user's code; dispose managed resources (if any)
            if (_csp != null) {
                _csp.Dispose();
                _csp = null;
            }

            // Dispose unmanaged resources (if any)
        }
    }

    #endregion
}