Skip to content

Instantly share code, notes, and snippets.

@AntonC9018
Last active September 5, 2024 11:34
Show Gist options
  • Save AntonC9018/d03eaf8f6ec1180b478bb6a635b0f3dc to your computer and use it in GitHub Desktop.
Save AntonC9018/d03eaf8f6ec1180b478bb6a635b0f3dc to your computer and use it in GitHub Desktop.
Password generator
using System.Security.Cryptography;
public static class GeneratePasswordHelper
{
private const byte _totalLength = 20;
public static string GeneratePassword(byte totalLength = _totalLength, RandomNumberGenerator? rng = null)
{
#pragma warning disable CA2000 // call Dispose
rng ??= RandomNumberGenerator.Create();
#pragma warning restore CA2000
var ret = string.Create(totalLength, (rng, totalLength), static (resultString, t) =>
{
var (rng, totalLength) = t;
byte categoryCount = 4;
// At least one symbol for each category.
byte remainingCount = (byte) (totalLength - categoryCount);
// https://www.wikiwand.com/en/articles/Stars_and_bars_(combinatorics)
Span<byte> counts = stackalloc byte[categoryCount];
{
Span<byte> bars = stackalloc byte[categoryCount];
rng.GetBytes(bars);
for (byte i = 0; i < categoryCount; i++)
{
bars[i] %= (byte)(remainingCount + 1);
}
bars.Sort();
counts[0] = (byte) (bars[0] + 1);
for (int i = 1; i < categoryCount; i++)
{
counts[i] = (byte) (bars[i] - bars[i - 1] + 1);
}
}
// Each category appears as many times as indicated in `counts`.
Span<byte> categoryAtIndex = stackalloc byte[totalLength];
{
byte runningIndex = 0;
for (byte i = 0; i < categoryCount; i++)
{
var count = counts[i];
for (byte j = 0; j < count; j++)
{
categoryAtIndex[runningIndex++] = i;
}
}
}
// Randomize the order.
ShuffleSmall(rng, categoryAtIndex);
Span<byte> randomNumbers = stackalloc byte[totalLength];
rng.GetBytes(randomNumbers);
for (byte i = 0; i < totalLength; i++)
{
var category = categoryAtIndex[i];
var num = randomNumbers[i];
var ch = GetChar((SymbolCategory) category, num);
resultString[i] = ch;
}
});
return ret;
}
private enum SymbolCategory
{
LowercaseLetter,
CapitalLetter,
Number,
SpecialCharacter,
}
const string specialCharacters = "!@#$%^&*";
private static byte GetCharacterCount(SymbolCategory category)
{
return category switch
{
SymbolCategory.LowercaseLetter => 'z' - 'a' + 1,
SymbolCategory.CapitalLetter => 'Z' - 'A' + 1,
SymbolCategory.Number => '9' - '0' + 1,
SymbolCategory.SpecialCharacter => (byte) specialCharacters.Length,
_ => throw new ArgumentOutOfRangeException(nameof(category)),
};
}
private static char GetChar(SymbolCategory category, byte number)
{
number %= GetCharacterCount(category);
return category switch
{
SymbolCategory.LowercaseLetter => (char)('a' + number),
SymbolCategory.CapitalLetter => (char)('A' + number),
SymbolCategory.Number => (char)('0' + number),
SymbolCategory.SpecialCharacter => specialCharacters[number],
_ => throw new ArgumentOutOfRangeException(nameof(category)),
};
}
private static void ShuffleSmall(RandomNumberGenerator rng, Span<byte> s)
{
Span<byte> permutations = stackalloc byte[s.Length];
rng.GetBytes(permutations);
{
for (byte i = 0; i < s.Length; i++)
{
var j = i + permutations[i] % (s.Length - i);
if (i != j)
{
// ReSharper disable once SwapViaDeconstruction
var t = s[i];
s[i] = s[j];
s[j] = t;
}
}
}
}
}
using System.Security.Cryptography;
public sealed class InsecureSeededRng : RandomNumberGenerator
{
private readonly Random _random;
public InsecureSeededRng(int seed) => _random = new Random(seed);
public override void GetBytes(byte[] data) => throw new InvalidOperationException();
#pragma warning disable CA5394 // not cryptographically secure
public override void GetBytes(Span<byte> span) => _random.NextBytes(span);
#pragma warning restore CA5394
}
public sealed class GeneratePasswordTests
{
[Fact]
public void GeneratePasswordContractTest()
{
using var rng = new InsecureSeededRng(69);
var password = GeneratePasswordHelper.GeneratePassword(20, rng);
Assert.Equal(20, password.Length);
Assert.Contains(password, char.IsUpper);
Assert.Contains(password, char.IsLower);
Assert.Contains(password, char.IsDigit);
Assert.Contains(password, c => !char.IsLetterOrDigit(c));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment