Skip to content

Instantly share code, notes, and snippets.

@michel-pi
Created April 1, 2019 07:21
Show Gist options
  • Save michel-pi/2b238f507af27922d2ec975f0e659b1a to your computer and use it in GitHub Desktop.
Save michel-pi/2b238f507af27922d2ec975f0e659b1a to your computer and use it in GitHub Desktop.
A static and thread safe cryptographic Random Number Generator (RNG)
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