This is a C# port of Bitwarden's random password algorithm.
A couple minor changes to this version from the original are:
- The exclusion of all vowels
- The addition of a no consecutive character check
- No ambiguous characters by default
This is a C# port of Bitwarden's random password algorithm.
A couple minor changes to this version from the original are:
| void Main() | |
| { | |
| var options = new PasswordOptions | |
| { | |
| Length = 13, | |
| MinLowercase = 2, | |
| MinNumbers = 2, | |
| MinSpecial = 1, | |
| MinUppercase = 2, | |
| UseLowercase = true, | |
| UseNumbers = true, | |
| UseSpecial = true, | |
| UseUppercase = true, | |
| }; | |
| RandomPasswordGenerator.Generate(options).Dump(); | |
| } | |
| public class RandomPasswordGenerator | |
| { | |
| public static string Generate(PasswordOptions options) | |
| { | |
| const string lowercaseChars = "bcdfghjkmnpqrstvwxyz"; // no aeiou | |
| const string uppercaseChars = "BCDFGHJKLMNPQRSTVWXYZ"; // no AEIOU | |
| const string numberChars = "23456789"; // no 01 | |
| const string specialChars = "!@#$%^&*"; | |
| var allChars = ""; | |
| var positions = new List<char>(); | |
| if (options.UseLowercase) | |
| { | |
| allChars += lowercaseChars; | |
| if (options.MinLowercase > 0) | |
| { | |
| for (var i = 0; i < options.MinLowercase; i++) | |
| { | |
| positions.Add('l'); | |
| } | |
| } | |
| } | |
| if (options.UseUppercase) | |
| { | |
| allChars += uppercaseChars; | |
| if (options.MinUppercase > 0) | |
| { | |
| for (var i = 0; i < options.MinUppercase; i++) | |
| { | |
| positions.Add('u'); | |
| } | |
| } | |
| } | |
| if (options.UseNumbers) | |
| { | |
| allChars += numberChars; | |
| if (options.MinNumbers > 0) | |
| { | |
| for (var i = 0; i < options.MinNumbers; i++) | |
| { | |
| positions.Add('n'); | |
| } | |
| } | |
| } | |
| if (options.UseSpecial) | |
| { | |
| allChars += specialChars; | |
| if (options.MinSpecial > 0) | |
| { | |
| for (var i = 0; i < options.MinSpecial; i++) | |
| { | |
| positions.Add('s'); | |
| } | |
| } | |
| } | |
| while (positions.Count < options.Length) | |
| { | |
| positions.Add('a'); | |
| } | |
| var rnd = new CryptoRandom(); | |
| ShuffleList(rnd, positions); | |
| var password = ""; | |
| for (var i = 0; i < positions.Count; i++) | |
| { | |
| string positionChars = null; | |
| switch (positions[i]) | |
| { | |
| case 'l': | |
| positionChars = lowercaseChars; | |
| break; | |
| case 'u': | |
| positionChars = uppercaseChars; | |
| break; | |
| case 'n': | |
| positionChars = numberChars; | |
| break; | |
| case 's': | |
| positionChars = specialChars; | |
| break; | |
| case 'a': | |
| positionChars = allChars; | |
| break; | |
| default: | |
| break; | |
| } | |
| var randomCharIndex = rnd.Next(0, positionChars.Length - 1); | |
| var randomChar = positionChars[randomCharIndex]; | |
| // no consecutive characters in a row | |
| if (i > 0 && password[i - 1] == randomChar) | |
| { | |
| i--; | |
| continue; | |
| } | |
| password += randomChar; | |
| } | |
| return password; | |
| } | |
| private static void ShuffleList<T>(Random rnd, List<T> list) | |
| { | |
| var length = list.Count - 1; | |
| while (length > 0) | |
| { | |
| var i = rnd.Next(0, length); | |
| (list[i], list[length]) = (list[length], list[i]); | |
| length--; | |
| } | |
| } | |
| } | |
| public class PasswordOptions | |
| { | |
| public bool UseLowercase { get; set; } | |
| public bool UseUppercase { get; set; } | |
| public bool UseNumbers { get; set; } | |
| public bool UseSpecial { get; set; } | |
| public int MinLowercase { get; set; } | |
| public int MinUppercase { get; set; } | |
| public int MinNumbers { get; set; } | |
| public int MinSpecial { get; set; } | |
| public int Length { get; set; } | |
| } | |
| // Copied from https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/september/net-matters-tales-from-the-cryptorandom | |
| public class CryptoRandom : Random | |
| { | |
| private RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); | |
| private byte[] _uint32Buffer = new byte[4]; | |
| public CryptoRandom() | |
| { | |
| } | |
| #pragma warning disable IDE0060 // Remove unused parameter | |
| public CryptoRandom(int ignoredSeed) | |
| { | |
| } | |
| #pragma warning restore IDE0060 // Remove unused parameter | |
| public override int Next() | |
| { | |
| _rng.GetBytes(_uint32Buffer); | |
| return BitConverter.ToInt32(_uint32Buffer, 0) & 0x7FFFFFFF; | |
| } | |
| public override int Next(int maxValue) | |
| { | |
| if (maxValue < 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); | |
| return Next(0, maxValue); | |
| } | |
| public override int Next(int minValue, int maxValue) | |
| { | |
| if (minValue > maxValue) throw new ArgumentOutOfRangeException(nameof(minValue)); | |
| if (minValue == maxValue) return minValue; | |
| var diff = maxValue - minValue; | |
| while (true) | |
| { | |
| _rng.GetBytes(_uint32Buffer); | |
| var rand = BitConverter.ToUInt32(_uint32Buffer, 0); | |
| var max = 1 + (long)uint.MaxValue; | |
| var remainder = max % diff; | |
| if (rand < max - remainder) | |
| { | |
| return (int)(minValue + (rand % diff)); | |
| } | |
| } | |
| } | |
| public override double NextDouble() | |
| { | |
| _rng.GetBytes(_uint32Buffer); | |
| var rand = BitConverter.ToUInt32(_uint32Buffer, 0); | |
| return rand / (1.0 + uint.MaxValue); | |
| } | |
| public override void NextBytes(byte[] buffer) | |
| { | |
| if (buffer is null) throw new ArgumentNullException(nameof(buffer)); | |
| _rng.GetBytes(buffer); | |
| } | |
| } |
There is a bug in the random next() - it never returns the max value...
I changed it to this
public override int Next(int minValue, int maxValue)
{
if (minValue > maxValue) throw new ArgumentOutOfRangeException(nameof(minValue));
if (minValue == maxValue) return minValue;
}