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;
}