Created
April 1, 2019 07:21
-
-
Save michel-pi/2b238f507af27922d2ec975f0e659b1a to your computer and use it in GitHub Desktop.
A static and thread safe cryptographic Random Number Generator (RNG)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace System.Security.Cryptography | |
{ | |
/// <summary> | |
/// Represents a static and thread safe cryptographic Random Number Generator (RNG) using the implementation provided by the cryptographic service provider (CSP). | |
/// </summary> | |
public static class SecureStaticRandom | |
{ | |
private const long MAX_EXCLUSIVE_UINT = 1 + (long)uint.MaxValue; | |
private const double MAX_EXCLUSIVE_UINT_DOUBLE = 1.0d + uint.MaxValue; | |
[ThreadStatic] private static RNGCryptoServiceProvider random; | |
[ThreadStatic] private static byte[] uintBuffer; | |
/// <summary> | |
/// Returns a non-negative random integer. | |
/// </summary> | |
/// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than <see cref="F:System.Int32.MaxValue" />.</returns> | |
public static int Next() | |
{ | |
return (int)GetRandomUInt32() & 0x7FFFFFFF; | |
} | |
/// <summary> | |
/// Returns a non-negative random integer that is less than the specified maximum. | |
/// </summary> | |
/// <param name="maxValue">The exclusive upper bound of the random number to be generated. <paramref name="maxValue" /> must be greater than or equal to 0.</param> | |
/// <returns>A 32-bit signed integer that is greater than or equal to 0, and less than <paramref name="maxValue" />; that is, the range of return values ordinarily includes 0 but not <paramref name="maxValue" />. However, if <paramref name="maxValue" /> equals 0, <paramref name="maxValue" /> is returned.</returns> | |
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="maxValue" /> is less than 0. </exception> | |
public static int Next(int maxValue) | |
{ | |
return Next(0, maxValue); | |
} | |
/// <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. <paramref name="maxValue" /> must be greater than or equal to <paramref name="minValue" />.</param> | |
/// <returns>A 32-bit signed integer greater than or equal to <paramref name="minValue" /> and less than <paramref name="maxValue" />; that is, the range of return values includes <paramref name="minValue" /> but not <paramref name="maxValue" />. If <paramref name="minValue" /> equals <paramref name="maxValue" />, <paramref name="minValue" /> is returned.</returns> | |
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="minValue" /> is greater than <paramref name="maxValue" />. </exception> | |
public static int Next(int minValue, int maxValue) | |
{ | |
if (minValue > maxValue) throw new ArgumentOutOfRangeException(nameof(minValue)); | |
if (maxValue < 1) throw new ArgumentOutOfRangeException(nameof(maxValue)); | |
if (minValue == maxValue) return minValue; | |
long delta = maxValue - minValue; | |
while (true) | |
{ | |
uint number = GetRandomUInt32(); | |
long remainder = MAX_EXCLUSIVE_UINT % delta; | |
if (number < MAX_EXCLUSIVE_UINT - remainder) | |
{ | |
return (int)(minValue + (number % delta)); | |
} | |
} | |
} | |
/// <summary> | |
/// Fills an array of bytes with a cryptographically strong sequence of random values. | |
/// </summary> | |
/// <param name="data">An array of bytes to contain random numbers.</param> | |
/// /// <exception cref="T:System.ArgumentNullException"><paramref name="data" /> is <see langword="null" />. </exception> | |
public static void GetBytes(byte[] data) | |
{ | |
if (random == null) InitThreadStaticFields(); | |
random.GetBytes(data); | |
} | |
/// <summary> | |
/// Returns a random boolean. | |
/// </summary> | |
/// <returns>A boolean value.</returns> | |
public static bool NextBool() | |
{ | |
return NextDouble() >= 0.5; | |
} | |
/// <summary> | |
/// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0. | |
/// </summary> | |
/// <returns>A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.</returns> | |
public static double NextDouble() | |
{ | |
return GetRandomUInt32() / MAX_EXCLUSIVE_UINT_DOUBLE; | |
} | |
/// <summary> | |
/// Returns a random floating-point number that is greater than or equal to 0.0, and less than <paramref name="maxValue" />. | |
/// </summary> | |
/// <param name="maxValue">The exclusive upper bound of the random number returned.</param> | |
/// <returns>A double-precision floating point number that is greater than or equal to 0, and less than <paramref name="maxValue" />; that is, the range of return values ordinarily includes 0 but not <paramref name="maxValue" />.</returns> | |
public static double NextDouble(int maxValue) | |
{ | |
if (double.IsInfinity(maxValue) | |
|| double.IsNaN(maxValue) | |
|| maxValue < 1.0d) throw new ArgumentOutOfRangeException(nameof(maxValue)); | |
return NextDouble() * maxValue; | |
} | |
/// <summary> | |
/// Returns a random floating-point number 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.</param> | |
/// <returns>A double-precision floating point number that is greater than or equal to <paramref name="minValue" />, and less than <paramref name="maxValue" />; that is, the range of return values ordinarily includes 0 but not <paramref name="maxValue" />.</returns> | |
public static double NextDouble(double minValue, double maxValue) | |
{ | |
if (double.IsInfinity(minValue) | |
|| double.IsNaN(minValue) | |
|| minValue >= maxValue) throw new ArgumentOutOfRangeException(nameof(maxValue)); | |
if (double.IsInfinity(maxValue) | |
|| double.IsNaN(maxValue) | |
|| maxValue < 1.0d) throw new ArgumentOutOfRangeException(nameof(maxValue)); | |
return minValue + NextDouble() * (maxValue - minValue); | |
} | |
/// <summary> | |
/// Fills the elements of a specified array of bytes with a cryptographically strong sequence of random nonzero values. | |
/// </summary> | |
/// <param name="data">An array of bytes to contain random numbers.</param> | |
/// /// <exception cref="T:System.ArgumentNullException"><paramref name="data" /> is <see langword="null" />. </exception> | |
public static void GetNonZeroBytes(byte[] data) | |
{ | |
if (random == null) InitThreadStaticFields(); | |
random.GetNonZeroBytes(data); | |
} | |
private static uint GetRandomUInt32() | |
{ | |
if (random == null) InitThreadStaticFields(); | |
random.GetBytes(uintBuffer); | |
return BitConverter.ToUInt32(uintBuffer, 0); | |
} | |
private static void InitThreadStaticFields() | |
{ | |
random = new RNGCryptoServiceProvider(); | |
uintBuffer = new byte[4]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment