using System.Security.Cryptography; using System.Text; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L9-1: 비밀번호 생성기 핸들러. "pwd" 프리픽스로 사용합니다. /// /// 예: pwd → 기본 16자 비밀번호 5개 생성 /// pwd 24 → 24자 비밀번호 5개 생성 /// pwd 32 strong → 32자 강력 옵션 (대소문자+숫자+특수) /// pwd 16 alpha → 알파벳+숫자만 (특수문자 제외) /// pwd 20 pin → 숫자만 (PIN 코드) /// pwd passphrase → 단어 조합 기억하기 쉬운 패스프레이즈 /// Enter → 클립보드에 복사. /// public class PasswordGenHandler : IActionHandler { public string? Prefix => "pwd"; public PluginMetadata Metadata => new( "PasswordGen", "비밀번호 생성기 — 길이 · 복잡도 · 패스프레이즈", "1.0", "AX"); private const string LowerChars = "abcdefghijklmnopqrstuvwxyz"; private const string UpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private const string DigitChars = "0123456789"; private const string SpecialChars = "!@#$%^&*()-_=+[]{}|;:,.<>?"; // 기억하기 쉬운 단어 목록 (간결한 영단어) private static readonly string[] WordList = [ "apple","bridge","cloud","dawn","eagle","flame","grape","harbor", "ivory","jungle","kite","lemon","maple","night","ocean","pearl", "quartz","river","storm","tiger","ultra","violet","water","xenon", "yellow","zenith","amber","blaze","cedar","delta","ember","frost", "glass","honey","iron","jade","knot","lunar","mango","nova", "orbit","prism","quest","range","solar","track","umbra","valor", ]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim().ToLowerInvariant(); var items = new List(); // 패스프레이즈 모드 if (q.StartsWith("passphrase") || q.StartsWith("phrase")) { var wordCount = 4; var parts2 = q.Split(' '); if (parts2.Length >= 2 && int.TryParse(parts2[1], out var wc)) wordCount = Math.Clamp(wc, 2, 8); items.Add(new LauncherItem( "패스프레이즈 생성", $"{wordCount}단어 조합 · 기억하기 쉬운 형식", null, null, Symbol: "\uE8D4")); for (int i = 0; i < 5; i++) { var phrase = GeneratePassphrase(wordCount); var strength = EstimateEntropy(phrase); items.Add(new LauncherItem( phrase, $"엔트로피: ~{strength}bit", null, ("copy", phrase), Symbol: "\uE8D4")); } return Task.FromResult>(items); } // 파라미터 파싱: [길이] [모드] int length = 16; string mode = "strong"; var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 1 && int.TryParse(parts[0], out var l)) { length = Math.Clamp(l, 4, 128); if (parts.Length >= 2) mode = parts[1]; } else if (parts.Length >= 1 && parts[0] is "strong" or "alpha" or "pin" or "simple") { mode = parts[0]; } // 문자 집합 결정 var charset = BuildCharset(mode); // 강도 표시 var modeLabel = mode switch { "alpha" => "알파뉴메릭 (대소문자+숫자)", "pin" => "숫자 PIN", "simple" => "간단 (소문자+숫자)", _ => "강력 (대소문자+숫자+특수)", }; items.Add(new LauncherItem( $"비밀번호 생성 {length}자", modeLabel, null, null, Symbol: "\uE8D4")); // 5개 후보 생성 for (int i = 0; i < 5; i++) { var pw = GeneratePassword(charset, length, mode); var strength = GetStrengthLabel(pw); items.Add(new LauncherItem( pw, strength, null, ("copy", pw), Symbol: "\uE8D4")); } // 옵션 안내 items.Add(new LauncherItem( "pwd <길이> alpha", "알파뉴메릭 (특수문자 제외)", null, null, Symbol: "\uE946")); items.Add(new LauncherItem( "pwd <길이> pin", "숫자만 (PIN 코드)", null, null, Symbol: "\uE946")); items.Add(new LauncherItem( "pwd passphrase", "단어 조합 패스프레이즈", null, null, Symbol: "\uE946")); return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("copy", string pw)) { try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(pw)); NotificationService.Notify("비밀번호", "클립보드에 복사했습니다."); } catch { /* 비핵심 */ } } return Task.CompletedTask; } // ── 헬퍼 ──────────────────────────────────────────────────────────────── private static string BuildCharset(string mode) => mode switch { "pin" => DigitChars, "alpha" => LowerChars + UpperChars + DigitChars, "simple" => LowerChars + DigitChars, _ => LowerChars + UpperChars + DigitChars + SpecialChars, }; private static string GeneratePassword(string charset, int length, string mode) { // strong 모드: 각 카테고리 최소 1개 보장 if (mode == "strong" && length >= 4) { var mandatory = new[] { RandomChar(LowerChars), RandomChar(UpperChars), RandomChar(DigitChars), RandomChar(SpecialChars), }; var remaining = length - mandatory.Length; var bulk = Enumerable.Range(0, remaining) .Select(_ => RandomChar(charset)) .ToList(); var all = mandatory.Concat(bulk).ToArray(); Shuffle(all); return new string(all); } // alpha/pin/simple return new string(Enumerable.Range(0, length) .Select(_ => RandomChar(charset)) .ToArray()); } private static string GeneratePassphrase(int wordCount) { var words = Enumerable.Range(0, wordCount) .Select(_ => WordList[RandomInt(WordList.Length)]); var num = RandomInt(9000) + 1000; var sep = new[] { "-", "_", ".", "!" }[RandomInt(4)]; return string.Join(sep, words) + sep + num; } private static char RandomChar(string charset) => charset[RandomInt(charset.Length)]; private static int RandomInt(int max) => (int)(RandomNumberGenerator.GetInt32(int.MaxValue) % max); private static void Shuffle(T[] arr) { for (int i = arr.Length - 1; i > 0; i--) { var j = RandomInt(i + 1); (arr[i], arr[j]) = (arr[j], arr[i]); } } private static string GetStrengthLabel(string pw) { var hasLower = pw.Any(char.IsLower); var hasUpper = pw.Any(char.IsUpper); var hasDigit = pw.Any(char.IsDigit); var hasSpecial = pw.Any(c => SpecialChars.Contains(c)); var types = new[] { hasLower, hasUpper, hasDigit, hasSpecial }.Count(b => b); var strength = (pw.Length, types) switch { ( >= 24, >= 4) => "매우 강함 🔐", ( >= 16, >= 3) => "강함 🔒", ( >= 12, >= 2) => "보통 🔑", _ => "약함 ⚠", }; return $"{strength} · {pw.Length}자"; } private static int EstimateEntropy(string pw) { // 간략 엔트로피 추정: log2(charset^length) var hasLower = pw.Any(char.IsLower); var hasUpper = pw.Any(char.IsUpper); var hasDigit = pw.Any(char.IsDigit); var hasSpecial = pw.Any(c => !char.IsLetterOrDigit(c)); var pool = (hasLower ? 26 : 0) + (hasUpper ? 26 : 0) + (hasDigit ? 10 : 0) + (hasSpecial ? 32 : 0); if (pool == 0) pool = 36; return (int)(pw.Length * Math.Log2(pool)); } }