//******************************************************************************************************
// Random.cs - Gbtc
//
// Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 01/04/2006 - J. Ritchie Carroll
// Generated original version of source code.
// 09/17/2008 - J. Ritchie Carroll
// Converted to C#.
// 02/16/2009 - Josh L. Patterson
// Edited Code Comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
// 11/17/2016 - Steven E. Chisholm
// Fixed the crypto random nature of numbers when a range is specified.
//
//******************************************************************************************************
using System;
using System.Security.Cryptography;
namespace GSF.Security.Cryptography
{
///
/// Generates cryptographically strong random numbers.
///
public static class Random
{
private static readonly RNGCryptoServiceProvider RandomNumberGenerator = new RNGCryptoServiceProvider();
///
/// Generates a semi cryptographically strong double-precision floating-point random number between zero and one. i.e. [0-1)
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static double Number
{
get
{
unchecked
{
return UInt32 / (uint.MaxValue + 1.0D);
}
}
}
///
/// Generates a semi cryptographically strong random decimal between zero and one. i.e. [0-1)
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static decimal Decimal
{
get
{
unchecked
{
return UInt64 / (ulong.MaxValue + 1.0M);
}
}
}
///
/// Generates a semi cryptographically strong random integer between specified values. i.e. [-)
///
/// A that is the low end of our range.
/// A that is the high end of our range.
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is generated between the and the , or an exception.
public static double Between(double startNumber, double stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return Number * (stopNumber - startNumber) + startNumber;
}
///
/// Fills an array of bytes with a cryptographically strong sequence of random values.
///
/// The array to fill with a cryptographically strong sequence of random values.
///
/// The length of the byte array determines how many cryptographically strong random bytes are produced.
/// This method is thread safe.
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// buffer is null.
public static void GetBytes(byte[] buffer)
{
RandomNumberGenerator.GetBytes(buffer);
}
///
/// Generates a cryptographically strong random boolean (i.e., a coin toss).
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static bool Boolean
{
get
{
byte[] value = new byte[1];
RandomNumberGenerator.GetBytes(value);
return (value[0] & 1) == 0;
}
}
///
/// Generates a cryptographically strong 8-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static byte Byte
{
get
{
byte[] value = new byte[1];
RandomNumberGenerator.GetBytes(value);
return value[0];
}
}
///
/// Generates a cryptographically strong 8-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static byte ByteBetween(byte startNumber, byte stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return (byte)(GetRandomNumberLessThan((uint)stopNumber - (uint)startNumber) + (uint)startNumber);
}
///
/// Generates a cryptographically strong 16-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static short Int16
{
get
{
byte[] value = new byte[2];
RandomNumberGenerator.GetBytes(value);
return BitConverter.ToInt16(value, 0);
}
}
///
/// Generates a cryptographically strong 16-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static short Int16Between(short startNumber, short stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return (short)(GetRandomNumberLessThan((int)stopNumber - (int)startNumber) + (int)startNumber);
}
///
/// Generates a cryptographically strong unsigned 16-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static ushort UInt16
{
get
{
byte[] value = new byte[2];
RandomNumberGenerator.GetBytes(value);
return BitConverter.ToUInt16(value, 0);
}
}
///
/// Generates a cryptographically strong unsigned 16-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static ushort UInt16Between(ushort startNumber, ushort stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return (ushort)(GetRandomNumberLessThan((uint)stopNumber - (uint)startNumber) + (uint)startNumber);
}
///
/// Generates a cryptographically strong 24-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static Int24 Int24
{
get
{
byte[] value = new byte[3];
RandomNumberGenerator.GetBytes(value);
return Int24.GetValue(value, 0);
}
}
///
/// Generates a cryptographically strong 24-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static Int24 Int24Between(Int24 startNumber, Int24 stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return (Int24)(GetRandomNumberLessThan((int)stopNumber - (int)startNumber) + (int)startNumber);
}
///
/// Generates a cryptographically strong unsigned 24-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static UInt24 UInt24
{
get
{
byte[] value = new byte[3];
RandomNumberGenerator.GetBytes(value);
return UInt24.GetValue(value, 0);
}
}
///
/// Generates a cryptographically strong unsigned 24-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static UInt24 Int24Between(UInt24 startNumber, UInt24 stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return (UInt24)(GetRandomNumberLessThan((uint)stopNumber - (uint)startNumber) + (uint)startNumber);
}
///
/// Generates a cryptographically strong 32-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static int Int32
{
get
{
byte[] value = new byte[4];
RandomNumberGenerator.GetBytes(value);
return BitConverter.ToInt32(value, 0);
}
}
///
/// Generates a cryptographically strong 32-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static int Int32Between(int startNumber, int stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return GetRandomNumberLessThan(stopNumber - startNumber) + startNumber;
}
///
/// Generates a cryptographically strong unsigned 32-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static uint UInt32
{
get
{
byte[] value = new byte[4];
RandomNumberGenerator.GetBytes(value);
return BitConverter.ToUInt32(value, 0);
}
}
///
/// Generates a cryptographically strong unsigned 32-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static uint UInt32Between(uint startNumber, uint stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return GetRandomNumberLessThan(stopNumber - startNumber) + startNumber;
}
///
/// Generates a cryptographically strong 64-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static long Int64
{
get
{
byte[] value = new byte[8];
RandomNumberGenerator.GetBytes(value);
return BitConverter.ToInt64(value, 0);
}
}
///
/// Generates a cryptographically strong 64-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static long Int64Between(long startNumber, long stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return GetRandomNumberLessThan(stopNumber - startNumber) + startNumber;
}
///
/// Generates a cryptographically strong unsigned 64-bit random integer.
///
/// The cryptographic service provider (CSP) cannot be acquired.
public static ulong UInt64
{
get
{
byte[] value = new byte[8];
RandomNumberGenerator.GetBytes(value);
return BitConverter.ToUInt64(value, 0);
}
}
///
/// Generates a cryptographically strong unsigned 64-bit random integer between specified values. i.e. [-)
///
/// The cryptographic service provider (CSP) cannot be acquired.
/// A that is the low end of our range.
/// A that is the high end of our range.
/// A that is generated between the and the .
public static ulong UInt64Between(ulong startNumber, ulong stopNumber)
{
if (stopNumber < startNumber)
throw new ArgumentException("stopNumber must be greater than startNumber");
return GetRandomNumberLessThan(stopNumber - startNumber) + startNumber;
}
///
/// Returns a cryptographically strong number that is less than the the supplied value
///
/// the max value to return exclusive
///
///
/// if 0 is provided, 0 is returned
///
private static uint GetRandomNumberLessThan(uint maxValue)
{
//A crypto number cannot be achieved via *, /, or % since these
//operations have rounding and uneven redistribution properties.
//Method: In order to get a crypto number <= maxValue
//A random number must be generated. If the random number is in range, it
//may be kept, otherwise a new number must be generated.
//To increase the likelihood that the number is in range,
//bit masking can be used on the higher order bits to
//create a 75% likelihood (on average) that the number will be in range.
//Exception cases where algorithm doesn't work
if (maxValue == 0 || maxValue == 1)
return 0;
//Determine the number of random bits that I need
int leadingZeroes = BitMath.CountLeadingZeros(maxValue);
uint value = UInt32;
//By shifting the value by the number of leading zeros, I'll have
//a number with the highest likelihood of being in range
while (value >> leadingZeroes >= maxValue)
{
//If the number is outside of range, all bits must
//be discarded and new ones generated. Not doing this
//technically alters the crypto-random nature of the value being generated.
value = UInt32;
}
return value >> leadingZeroes;
}
///
/// Returns a cryptographically strong number that is less than the the supplied value
///
/// the max value to return exclusive
///
///
/// if 0 is provided, 0 is returned
///
private static ulong GetRandomNumberLessThan(ulong maxValue)
{
//A crypto number cannot be achieved via *, /, or % since these
//operations have rounding and uneven redistribution properties.
//Method: In order to get a crypto number <= maxValue
//A random number must be generated. If the random number is in range, it
//may be kept, otherwise a new number must be generated.
//To increase the likelihood that the number is in range,
//bit masking can be used on the higher order bits to
//create a 75% likelihood (on average) that the number will be in range.
//Exception cases where algorithm doesn't work
if (maxValue == 0 || maxValue == 1)
return 0;
//Determine the number of random bits that I need
int leadingZeroes = BitMath.CountLeadingZeros(maxValue);
ulong value = UInt64;
//By shifting the value by the number of leading zeros, I'll have
//a number with the highest likelihood of being in range
while (value >> leadingZeroes >= maxValue)
{
//If the number is outside of range, all bits must
//be discarded and new ones generated. Not doing this
//technically alters the crypto-random nature of the value being generated.
value = UInt64;
}
return value >> leadingZeroes;
}
///
/// Returns a cryptographically strong number that is less the the supplied value
///
/// the max value to return exclusive
///
///
/// A number less than a negative number rolls down to long.MinValue, then to long.MaxValue
/// if 0 is provided, 0 is returned
///
private static long GetRandomNumberLessThan(long maxValue)
{
return (long)GetRandomNumberLessThan((ulong)maxValue);
}
///
/// Returns a cryptographically strong number that is less the the supplied value
///
/// the max value to return exclusive
///
///
/// A number less than a negative number rolls down to int.MinValue, then to int.MaxValue
/// if 0 is provided, 0 is returned
///
private static int GetRandomNumberLessThan(int maxValue)
{
return (int)GetRandomNumberLessThan((uint)maxValue);
}
}
}