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)); } } }