Files

53 lines
2.7 KiB
C#

using System.Security.Cryptography;
using System.Text;
namespace QSafe.Core;
public enum PasswordMode { Complex, Digits, Memorable }
public static class PasswordGenerator
{
private static readonly string[] Words = LoadWords();
private static string[] LoadWords()
{
using var stream = typeof(PasswordGenerator).Assembly.GetManifestResourceStream("QSafe.PasswordWords")
?? throw new InvalidOperationException("Missing password word list");
using var reader = new StreamReader(stream);
var words = reader.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (words.Length != 256 || words.Distinct().Count() != 256) throw new InvalidOperationException("Invalid password word list");
return words;
}
public static string Generate(PasswordMode mode, int length, bool symbols = true, bool digits = true)
{
switch (mode)
{
case PasswordMode.Complex: return VaultCrypto.Generate(length, symbols);
case PasswordMode.Digits:
if (length is < 4 or > 128) throw new ArgumentOutOfRangeException(nameof(length));
return new string(Enumerable.Range(0, length).Select(_ => (char)('0' + RandomNumberGenerator.GetInt32(10))).ToArray());
case PasswordMode.Memorable:
if (length is < 2 or > 12) throw new ArgumentOutOfRangeException(nameof(length));
const string signs = "!@#$%&*+-=?";
// Treat each word as a whole: extras only go before, between or after words.
var boundaries = Enumerable.Range(0, length + 1).Select(_ => new StringBuilder()).ToArray();
void InsertAtBoundary(char character)
{
var boundary = boundaries[RandomNumberGenerator.GetInt32(boundaries.Length)];
boundary.Insert(RandomNumberGenerator.GetInt32(boundary.Length + 1), character);
}
if (digits) for (int i = 0; i < 2; i++) InsertAtBoundary((char)('0' + RandomNumberGenerator.GetInt32(10)));
if (symbols) for (int i = 1; i < length; i++) InsertAtBoundary(signs[RandomNumberGenerator.GetInt32(signs.Length)]);
var value = new StringBuilder();
for (int i = 0; i < length; i++)
{
string word = Words[RandomNumberGenerator.GetInt32(Words.Length)];
value.Append(boundaries[i]).Append(char.ToUpperInvariant(word[0])).Append(word.AsSpan(1));
}
return value.Append(boundaries[length]).ToString();
default: throw new ArgumentOutOfRangeException(nameof(mode));
}
}
}