using Lskj.Control; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Security.Cryptography; using System.Security.Principal; using System.Text; using System.Windows.Forms; namespace Lskj.GetSecretKey { public partial class LoginFrm : BaseForm { private ContextMenuStrip _ctx; public LoginFrm() { InitializeComponent(); SecureUserStore.EnsureInitialized(); // 初始化本地存储(含默认账号) InitUi(); WireEvents(); } #region UI & 事件 private void InitUi() { // 密码框设置为 * 掩码(兼容 TextBox / DevExpress TextEdit) SetPasswordMask(passWordEidt); // 回填上次成功登录账号 var last = SecureUserStore.GetLastUser(); if (!string.IsNullOrEmpty(last)) { userNameEdit.Text = last; passWordEidt.Focus(); } else userNameEdit.Focus(); // 提示 try { setPanel.Cursor = Cursors.Hand; var tip = new ToolTip(); tip.SetToolTip(setPanel, "左键:修改密码(需当前密码验证)\r\n右键:更多操作(重置密码…)"); } catch { /* ignore */ } // 右键菜单 _ctx = new ContextMenuStrip(); var miChange = new ToolStripMenuItem("修改密码…", null, (s, e) => DoChangePassword()); var miReset = new ToolStripMenuItem("重置密码…", null, (s, e) => DoResetPassword()); _ctx.Items.AddRange(new ToolStripItem[] { miChange, new ToolStripSeparator(), miReset }); setPanel.ContextMenuStrip = _ctx; } private void WireEvents() { // 回车登录 passWordEidt.KeyDown += (s, e) => { if (e.KeyCode == Keys.Enter) TryLogin(); }; // 左键点击 setPanel -> 修改密码(需当前密码验证通过) setPanel.Click += (s, e) => { if (e is MouseEventArgs me && me.Button == MouseButtons.Right) return; DoChangePassword(); }; // 右键已经由 ContextMenuStrip 处理 } #endregion #region 登录/改密/重置 private void TryLogin() { string user = (userNameEdit.Text ?? "").Trim(); string pwd = passWordEidt.Text ?? ""; if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pwd)) { MessageBox.Show("请输入账号和密码。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (SecureUserStore.Validate(user, pwd)) { SecureUserStore.SetLastUser(user); // 记住本次成功登录账号 this.DialogResult = DialogResult.OK; this.Close(); } else { MessageBox.Show("账号或密码错误。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); passWordEidt.SelectAll(); passWordEidt.Focus(); } } private void DoChangePassword() { string user = (userNameEdit.Text ?? "").Trim(); string currPwd = passWordEidt.Text ?? ""; if (string.IsNullOrEmpty(user)) { MessageBox.Show("请先在账号框输入要修改密码的账号(例如:管理员、管理员1)。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); userNameEdit.Focus(); return; } if (!SecureUserStore.Exists(user)) { MessageBox.Show("账号不存在,仅支持:管理员、管理员1。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); userNameEdit.Focus(); userNameEdit.SelectAll(); return; } // 必须先通过“账号+当前密码”验证 if (!SecureUserStore.Validate(user, currPwd)) { MessageBox.Show("请先在密码框输入该账号的当前密码并验证通过,然后再修改。", "需要验证", MessageBoxButtons.OK, MessageBoxIcon.Warning); passWordEidt.Focus(); passWordEidt.SelectAll(); return; } using (var dlg = new ChangePasswordDialog(user)) { if (dlg.ShowDialog(this) == DialogResult.OK) { SecureUserStore.SetPassword(user, dlg.NewPassword); MessageBox.Show("密码已更新并安全保存到本地。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } } } private void DoResetPassword() { // 仅管理员权限允许重置 if (!SecurityUtil.IsAdministrator()) { MessageBox.Show("需要以【管理员身份运行】程序后才能重置密码。", "权限不足", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } using (var dlg = new ResetConfirmDialog()) { if (dlg.ShowDialog(this) != DialogResult.OK) return; // 校验重置口令(你可以修改 SecureReset.ResetSecret 自定义口令) if (!SecureReset.VerifyResetCode(dlg.ResetCode)) { MessageBox.Show("重置口令不正确。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } string user = (userNameEdit.Text ?? "").Trim(); if (string.IsNullOrEmpty(user)) { // 未指定账号 -> 询问是否重置所有内置账号 if (MessageBox.Show("未填写账号,是否重置所有内置账号为默认密码 123456?", "重置确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { foreach (var u in SecureUserStore.BuiltinUsers) SecureUserStore.ResetToDefault(u); MessageBox.Show("所有内置账号已重置为 123456。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } } else { if (!SecureUserStore.Exists(user)) { MessageBox.Show("账号不存在,无法重置。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (MessageBox.Show($"确定将「{user}」密码重置为 123456?", "重置确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { SecureUserStore.ResetToDefault(user); MessageBox.Show("已重置为 123456。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); } } } #endregion #region 工具:为控件设置 * 掩码(兼容 TextBox / DevExpress TextEdit) private static void SetPasswordMask(System.Windows.Forms.Control ctrl) { try { var t = ctrl.GetType(); var propPwdChar = t.GetProperty("PasswordChar"); if (propPwdChar != null && propPwdChar.CanWrite) propPwdChar.SetValue(ctrl, '*', null); var propUseSys = t.GetProperty("UseSystemPasswordChar"); if (propUseSys != null && propUseSys.CanWrite) propUseSys.SetValue(ctrl, true, null); var propProperties = t.GetProperty("Properties"); if (propProperties != null) { var propsObj = propProperties.GetValue(ctrl, null); if (propsObj != null) { var px = propsObj.GetType(); var p1 = px.GetProperty("PasswordChar"); if (p1 != null && p1.CanWrite) p1.SetValue(propsObj, '*', null); var p2 = px.GetProperty("UseSystemPasswordChar"); if (p2 != null && p2.CanWrite) p2.SetValue(propsObj, true, null); } } } catch { /* ignore */ } } #endregion } #region 安全工具 & 重置口令校验 internal static class SecurityUtil { public static bool IsAdministrator() { try { using (var identity = WindowsIdentity.GetCurrent()) { var principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } } catch { return false; } } public static string Sha256(string s) { using (var sha = SHA256.Create()) { var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(s ?? "")); var sb = new StringBuilder(bytes.Length * 2); foreach (var b in bytes) sb.Append(b.ToString("x2")); return sb.ToString(); } } } internal static class SecureReset { // ★ 重置口令(明文)。建议你改成自己的:仅内部人员知悉。 private const string ResetSecret = "Lskj#Reset2025"; private static readonly string ResetHash = SecurityUtil.Sha256(ResetSecret); public static bool VerifyResetCode(string input) => string.Equals(SecurityUtil.Sha256(input ?? ""), ResetHash, StringComparison.OrdinalIgnoreCase); } #endregion #region 本地安全存储(DPAPI 加密 + 记住上次账号) internal static class SecureUserStore { // %LOCALAPPDATA%\Lskj.GetSecretKey\ private static readonly string AppDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Lskj.GetSecretKey"); private static readonly string StorePath = Path.Combine(AppDir, "users.sec"); // 用户: 密文 private static readonly string LastUserPath = Path.Combine(AppDir, "lastuser.dat"); // 纯文本:上一次成功登录账号 private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("Lskj.GetSecretKey|DPAPI|v1"); internal static readonly string[] BuiltinUsers = new[] { "管理员", "管理员1" }; // 供外部枚举使用 private const string DefaultPassword = "123456"; // 缓存:用户名 -> Base64(Protect(UTF8(pwd))) private static readonly Dictionary _cipherMap = new Dictionary(StringComparer.Ordinal); private static bool _initialized; private static readonly object _lock = new object(); public static void EnsureInitialized() { if (_initialized) return; lock (_lock) { if (_initialized) return; Directory.CreateDirectory(AppDir); if (!File.Exists(StorePath)) { foreach (var u in BuiltinUsers) _cipherMap[u] = ProtectToBase64(DefaultPassword); Persist(); } else { Load(); bool changed = false; foreach (var u in BuiltinUsers) if (!_cipherMap.ContainsKey(u)) { _cipherMap[u] = ProtectToBase64(DefaultPassword); changed = true; } if (changed) Persist(); } _initialized = true; } } public static bool Exists(string user) { EnsureInitialized(); return _cipherMap.ContainsKey(user); } public static bool Validate(string user, string plainPwd) { EnsureInitialized(); if (!_cipherMap.TryGetValue(user, out var b64)) return false; try { return UnprotectFromBase64(b64) == (plainPwd ?? ""); } catch { return false; } } public static void SetPassword(string user, string newPwd) { EnsureInitialized(); if (string.IsNullOrEmpty(user)) return; _cipherMap[user] = ProtectToBase64(newPwd ?? ""); Persist(); } public static void ResetToDefault(string user) { EnsureInitialized(); if (string.IsNullOrEmpty(user)) return; _cipherMap[user] = ProtectToBase64(DefaultPassword); Persist(); } public static void SetLastUser(string user) { try { File.WriteAllText(LastUserPath, user ?? "", Encoding.UTF8); } catch { /* ignore */ } } public static string GetLastUser() { try { return File.Exists(LastUserPath) ? (File.ReadAllText(LastUserPath, Encoding.UTF8).Trim()) : ""; } catch { return ""; } } private static void Load() { _cipherMap.Clear(); foreach (var line in File.ReadAllLines(StorePath, Encoding.UTF8)) { if (string.IsNullOrWhiteSpace(line)) continue; var idx = line.IndexOf('='); if (idx <= 0) continue; var key = line.Substring(0, idx); var val = line.Substring(idx + 1); if (!_cipherMap.ContainsKey(key)) _cipherMap[key] = val; } } private static void Persist() { var sb = new StringBuilder(); foreach (var kv in _cipherMap) sb.Append(kv.Key).Append('=').Append(kv.Value).Append('\n'); File.WriteAllText(StorePath, sb.ToString(), Encoding.UTF8); } private static string ProtectToBase64(string plain) { var bytes = Encoding.UTF8.GetBytes(plain ?? ""); var cipher = ProtectedData.Protect(bytes, Entropy, DataProtectionScope.CurrentUser); return Convert.ToBase64String(cipher); } private static string UnprotectFromBase64(string base64) { var cipher = Convert.FromBase64String(base64 ?? ""); var plain = ProtectedData.Unprotect(cipher, Entropy, DataProtectionScope.CurrentUser); return Encoding.UTF8.GetString(plain); } } #endregion #region 修改密码对话框 internal sealed class ChangePasswordDialog : Form { private readonly string _user; private TextBox txtNew, txtConfirm; private Button btnOk, btnCancel; public string NewPassword { get; private set; } public ChangePasswordDialog(string user) { _user = user; BuildUi(); } private void BuildUi() { this.Text = $"修改密码 - {_user}"; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.StartPosition = FormStartPosition.CenterParent; this.ClientSize = new Size(340, 170); this.MaximizeBox = false; this.MinimizeBox = false; var lbl1 = new Label { Text = "新密码:", AutoSize = true, Location = new Point(20, 28) }; var lbl2 = new Label { Text = "确认新密码:", AutoSize = true, Location = new Point(20, 68) }; txtNew = new TextBox { Location = new Point(130, 24), Width = 180, UseSystemPasswordChar = true, PasswordChar = '*' }; txtConfirm = new TextBox { Location = new Point(130, 64), Width = 180, UseSystemPasswordChar = true, PasswordChar = '*' }; btnOk = new Button { Text = "确定", DialogResult = DialogResult.OK, Location = new Point(130, 110), Width = 80 }; btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel, Location = new Point(230, 110), Width = 80 }; btnOk.Click += (s, e) => { if (string.IsNullOrEmpty(txtNew.Text)) { MessageBox.Show("新密码不能为空。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.DialogResult = DialogResult.None; return; } if (txtNew.Text != txtConfirm.Text) { MessageBox.Show("两次输入的密码不一致。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.DialogResult = DialogResult.None; return; } NewPassword = txtNew.Text; }; this.Controls.AddRange(new System.Windows.Forms.Control[] { lbl1, txtNew, lbl2, txtConfirm, btnOk, btnCancel }); this.AcceptButton = btnOk; this.CancelButton = btnCancel; } } #endregion #region 重置口令对话框 internal sealed class ResetConfirmDialog : Form { private TextBox txtCode; private Button btnOk, btnCancel; public string ResetCode { get; private set; } public ResetConfirmDialog() { BuildUi(); } private void BuildUi() { this.Text = "输入重置口令"; this.FormBorderStyle = FormBorderStyle.FixedDialog; this.StartPosition = FormStartPosition.CenterParent; this.ClientSize = new Size(360, 150); this.MaximizeBox = false; this.MinimizeBox = false; var lbl = new Label { Text = "重置口令:", AutoSize = true, Location = new Point(20, 30) }; txtCode = new TextBox { Location = new Point(100, 26), Width = 220, UseSystemPasswordChar = true, PasswordChar = '*' }; btnOk = new Button { Text = "确定", DialogResult = DialogResult.OK, Location = new Point(160, 80), Width = 75 }; btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel, Location = new Point(245, 80), Width = 75 }; btnOk.Click += (s, e) => { if (string.IsNullOrEmpty(txtCode.Text)) { MessageBox.Show("请输入重置口令。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.DialogResult = DialogResult.None; return; } ResetCode = txtCode.Text; }; this.Controls.AddRange(new System.Windows.Forms.Control[] { lbl, txtCode, btnOk, btnCancel }); this.AcceptButton = btnOk; this.CancelButton = btnCancel; } } #endregion }