Last active
September 5, 2024 11:34
-
-
Save AntonC9018/d03eaf8f6ec1180b478bb6a635b0f3dc to your computer and use it in GitHub Desktop.
Password generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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