Skip to content

Instantly share code, notes, and snippets.

@AdamWhiteHat
Last active December 22, 2020 12:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdamWhiteHat/177e253ca400c757f7645a6cc65ba958 to your computer and use it in GitHub Desktop.
Save AdamWhiteHat/177e253ca400c757f7645a6cc65ba958 to your computer and use it in GitHub Desktop.
Cryptographically secure random number generator. Safe, secure, & correct implementation. Numbers are evenly distributed across entire range; avoids modulo bias. Internal buffers always cleared after every value returned--leaves nothing in memory. Singleton intended to be instantiated only once. Remember to call Dispose.
/// <summary>
/// Cryptographically secure random number generator. Features a safe, secure, and correct implementation.
/// Numbers are evenly distributed across the whole range; avoids modulo bias.
/// Internal buffers always cleared after every value returned--leaves nothing in memory.
/// Singleton intended to be instantiated only once for the lifetime of the application.
/// Remember to call Dispose when no longer needed.
/// </summary>
public static class CryptoRandomSingleton
{
private static byte[] rngBytes4 = new byte[4];
private static byte[] rngBytes8 = new byte[8];
private static RNGCryptoServiceProvider _rng;
static CryptoRandomSingleton() { _rng = new RNGCryptoServiceProvider(); }
private static void ClearBuffer(byte[] buffer)
{
if (buffer != null)
{
int counter = buffer.Length - 1;
while (counter >= 0)
{
buffer[counter] = byte.MinValue;
counter--;
}
counter = 0;
}
}
public static void Dispose()
{
if (_rng != null)
{
_rng.Dispose();
_rng = null;
}
if (rngBytes4 != null)
{
ClearBuffer(rngBytes4);
rngBytes4 = null;
}
if (rngBytes8 != null)
{
ClearBuffer(rngBytes8);
rngBytes8 = null;
}
}
public static void NextBytes(byte[] buffer)
{
_rng.GetBytes(buffer);
}
public static long Next()
{
_rng.GetBytes(rngBytes8);
long result = Math.Abs(BitConverter.ToInt64(rngBytes8, 0));
ClearBuffer(rngBytes8);
return result;
}
public static double NextDouble()
{
_rng.GetBytes(rngBytes8);
double result = Math.Abs(BitConverter.ToInt64(rngBytes8, 0) * (1.0 / long.MaxValue));
ClearBuffer(rngBytes8);
return result;
}
public static double NextDouble(double max)
{
return NextDouble() * max;
}
public static double NextDouble(double lower, double upper)
{
return lower + ((upper - lower) * NextDouble());
}
public static IEnumerable<BigInteger> RandomRange(BigInteger lower, BigInteger upper, int quantity)
{
while (quantity-- > 0)
{
yield return RandomRange(lower, upper);
}
yield break;
}
public static BigInteger RandomRange(BigInteger lower, BigInteger upper)
{
if (lower > upper) { throw new ArgumentOutOfRangeException("Upper must be greater than lower"); }
// long implementation
if (lower <= long.MaxValue && upper <= long.MaxValue)
{
BigInteger range;
while (true)
{
range = lower + (long)(((long)upper - (long)lower) * NextDouble());
if (range >= lower && range <= upper)
{
return range;
}
}
}
else // BigInteger implementation
{
return RandomRangeBigInteger(lower, upper);
}
}
private static BigInteger RandomRangeBigInteger(BigInteger lower, BigInteger upper)
{
if (lower > upper) { throw new ArgumentOutOfRangeException("Upper must be greater than upper"); }
BigInteger delta = upper - lower;
byte[] deltaBytes = delta.ToByteArray();
byte[] buffer = new byte[deltaBytes.Length];
ClearBuffer(deltaBytes);
deltaBytes = null;
BigInteger result;
while (true)
{
NextBytes(buffer);
result = new BigInteger(buffer) + lower;
if (result >= lower && result <= upper)
{
ClearBuffer(buffer);
return result;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment