53 lines
2.7 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|