Skip to content

Instantly share code, notes, and snippets.

@xt0rted
Last active September 18, 2023 20:27
Show Gist options
  • Save xt0rted/ec780f5d404fb2b6967b18d05da033ca to your computer and use it in GitHub Desktop.
Save xt0rted/ec780f5d404fb2b6967b18d05da033ca to your computer and use it in GitHub Desktop.
C# port of Bitwarden's random password algorithm

README

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
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);
}
}
@dt250209
Copy link

dt250209 commented Sep 18, 2023

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;

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment