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); | |
} | |
} |
@dantclee I used this to generate temporary passwords that were sent to users. Based on prior experiences it was safer to exclude vowels so they couldn't contain offensive words than it was to risk an angry user.
Thank you for your reply.
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;
var diff = maxValue - minValue;
_rng.GetBytes(_uint32Buffer);
var rand = BitConverter.ToUInt32(_uint32Buffer, 0);
var remainder = rand % (diff + 1);
return minValue + (int) remainder;
}
@dantclee I used this to generate temporary passwords that were sent to users. Based on prior experiences it was safer to exclude vowels so they couldn't contain offensive words than it was to risk an angry user.