There are two issues in the accepted answer.
- It does not dispose the disposable csp correctly
- When minvalue equals to maxvalue, it throws error (standard random method does not)
using System;
using System.Security.Cryptography;
namespace CovidMassTesting.Helpers
{
/// <summary>
/// Secure random generator
///
/// https://stackoverflow.com/questions/42426420/how-to-generate-a-cryptographically-secure-random-integer-within-a-range
/// </summary>
public class RandomGenerator : IDisposable
{
private readonly RNGCryptoServiceProvider csp;
/// <summary>
/// Constructor
/// </summary>
public RandomGenerator()
{
csp = new RNGCryptoServiceProvider();
}
/// <summary>
/// Get random value
/// </summary>
/// <param name="minValue"></param>
/// <param name="maxExclusiveValue"></param>
/// <returns></returns>
public int Next(int minValue, int maxExclusiveValue)
{
if (minValue == maxExclusiveValue) return minValue;
if (minValue > maxExclusiveValue)
{
throw new ArgumentOutOfRangeException($"{nameof(minValue)} must be lower than {nameof(maxExclusiveValue)}");
}
var diff = (long)maxExclusiveValue - minValue;
var upperBound = uint.MaxValue / diff * diff;
uint ui;
do
{
ui = GetRandomUInt();
} while (ui >= upperBound);
return (int)(minValue + (ui % diff));
}
private uint GetRandomUInt()
{
var randomBytes = GenerateRandomBytes(sizeof(uint));
return BitConverter.ToUInt32(randomBytes, 0);
}
private byte[] GenerateRandomBytes(int bytesNumber)
{
var buffer = new byte[bytesNumber];
csp.GetBytes(buffer);
return buffer;
}
private bool _disposed;
/// <summary>
/// Public implementation of Dispose pattern callable by consumers.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Protected implementation of Dispose pattern.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// Dispose managed state (managed objects).
csp?.Dispose();
}
_disposed = true;
}
}
}
Usage
/// <summary>
/// Generates a Random Password
/// respecting the given strength requirements.
/// </summary>
/// <param name="opts">A valid PasswordOptions object
/// containing the password strength requirements.</param>
/// <returns>A random password</returns>
public static string GenerateRandomPassword(PasswordOptions opts = null)
{
if (opts == null) opts = new PasswordOptions()
{
RequiredLength = 10,
RequiredUniqueChars = 4,
RequireDigit = true,
RequireLowercase = true,
RequireNonAlphanumeric = true,
RequireUppercase = true
};
string[] randomChars = new[] {
"ABCDEFGHJKLMNOPQRSTUVWXYZ", // uppercase
"abcdefghijkmnopqrstuvwxyz", // lowercase
"0123456789", // digits
"!@$?_-" // non-alphanumeric
};
using RandomGenerator rand = new RandomGenerator();
List<char> chars = new List<char>();
if (opts.RequireUppercase)
chars.Insert(rand.Next(0, chars.Count),
randomChars[0][rand.Next(0, randomChars[0].Length)]);
if (opts.RequireLowercase)
chars.Insert(rand.Next(0, chars.Count),
randomChars[1][rand.Next(0, randomChars[1].Length)]);
if (opts.RequireDigit)
chars.Insert(rand.Next(0, chars.Count),
randomChars[2][rand.Next(0, randomChars[2].Length)]);
if (opts.RequireNonAlphanumeric)
chars.Insert(rand.Next(0, chars.Count),
randomChars[3][rand.Next(0, randomChars[3].Length)]);
for (int i = chars.Count; i < opts.RequiredLength
|| chars.Distinct().Count() < opts.RequiredUniqueChars; i++)
{
string rcs = randomChars[rand.Next(0, randomChars.Length)];
chars.Insert(rand.Next(0, chars.Count),
rcs[rand.Next(0, rcs.Length)]);
}
return new string(chars.ToArray());
}