【C#】聊聊不需要记密码的密码管理补充帖 —— 具体实现
开篇第一句话,就是“小白继续,有实际经验的兄弟们可以洗洗睡了”,因为这个 Lite 版是个实验性的实现,也由于水平原因源码不忍直视,所以如果你坚持看完了,请留下宝贵意见。
以下,干货:
基本模式:
程序使用“钥匙”对用户需要加密的内容进行自动加密,加密完成后,导出数据和“钥匙”为文件,用户自行保管文件即可。解密时,告诉程序要用哪个钥匙,程序无需用户任何输入即可自动完成解密,解密失败也不会影响用户数据。
此模式好处:
用户无需关心数据如何保存,更不必管“钥匙”具体什么样(无需指定密码),只要在使用时告诉程序“来,用这个钥匙,给我解这个数据”,就像插入钥匙打开锁头一样,至于钥匙长什么样,管它呢!
不能被破解吗?
理论上哪有不能被破解的东西,但是这种方式也很安全,因为你需要满足下面几种方式才能被破解:
1. 暴力算法,纯技术流,黑客拿到你的数据文件,看到一串乱码,但由于黑客经验极其丰富一看就知道大概是什么算法算出来的,好,他就开始暴力破解....唉,我不好说用多长时间,但是平民小白被牛B黑客这么死心塌地破解的概率恐怕比中国男足进世界杯还小吧。
2. 黑客必须同时得到你的数据文件和“钥匙”文件 & 还得知道你的A钥匙就是开A数据的 & 还得知道怎么使用钥匙开你的数据文件(程序逻辑),这概率...也够小的吧。
数据逻辑:
1. 程序里,我设定了四个基础数据,来组成一个“隐私内容项”,分别是标题、关键字、加密内容和校验码;

#region Statements /// <summary> /// 标题 /// </summary> private string _title; /// <summary> /// 关键内容,比如登录账号 /// </summary> private string _keyword; /// <summary> /// 加密后的密文 /// </summary> private string _ciphertext; /// <summary> /// 校验值 /// </summary> private string _checkcode; #endregion
2. 隐私内容类是承载用户信息的基本单元,本身没有功能,所以设计一个管理它使用它的类,暂时叫做“内容管理类”吧。内容管理类的功能是接收界面反馈,添加、移除“隐私内容项”,使用“加密服务”来实现加密解密功能等等;
3. 内容管理类自己没有加解密功能,是要调用实现加解密功能的类,暂时成为“密钥服务”吧,密钥服务要实现密钥新生、密钥导入、密钥导出、加密、解密、创建Hash、验证等功能;

public class CryptogramService { public byte[] Encrypt(byte[] data){ } public byte[] Decrypt(byte[] data){ } public CryptogramService LoadKey(string path){ } public byte[] GetKey(){ } }
4. 内容管理类可以将所有“隐私内容项”导出成文件;
5. 密钥服务类可以将密钥信息保存成文件;
以上代码仅供参考,实际应该被实现成接口,和调用方实现松耦合,我在程序里暂时写死了 ^^,至此 iPassword Lite 主要功能所使用到的数据都描述了,总结起来就是“内容管理”接收用户输入,创建一个“隐私内容”,调用“密钥服务”加密“隐私内容”
密钥服务:
1. 创建新钥匙:程序启动时,默认创建新的“钥匙”,钥匙的齿什么样用户不用管,其实程序也不知道;
2. 导入钥匙:程序可以导入用户指定的“钥匙”,导入钥匙的作用是让用户使用自选的钥匙来开锁;
3. 导出钥匙:程序可以保存当前使用的钥匙为文件;
界面实现:
在 iPassword Lite 中使用了 ListBox 自绘的方式呈现,这恐怕也是整个程序中唯一可以给其他朋友借鉴一二的地方,倒不是多高明,是因为其它的内容实在没什么展示代码的意义。一步步说吧
1. 设置 ListBox,其 DrawMode 设置成 OwnerDrawVariable 就可以自绘了;
2. 绘制成什么样,我现在的做法是 头像 + 标题 + 描述文字(描述文字暂时是关键字内容)的现实方式,左侧头像,右侧由标题和描述文字上下左对齐排列;
3. 测量项目,在绘制前要设定好 ListBox 的每个 Item 的大小,宽度好办,我就设置 ListBox 的 ClientSize 就可以了,高度是个问题,我的方式是上空白 + 头像高度 + 下空白的方式,但是我的头像高度又由标题文字和描述文字及留白组成,所以实际计算方式是【上留白 + 标题 + 描述文字 + 下留白】来组成,下面是实际代码

private void profileListBox_MeasureItem(object sender, MeasureItemEventArgs e) { // 高度 = 上下通用间隔 + 标题和描述所有文本间隔 this.MeasureTitleHeight = TextRenderer.MeasureText("中", this.FontForTitle).Height; this.MeasureDescriptionHeight = TextRenderer.MeasureText("中", this.FontForDescription).Height; var allTextHeight = this.MeasureTitleHeight + this.MeasureDescriptionHeight + this.TextPadding * 4; e.ItemHeight = this.ItemPadding * 2 + allTextHeight; e.ItemWidth = profileListBox.ClientRectangle.Width; // 设置头像尺寸 this.HeadshotSize = new Size(allTextHeight, allTextHeight); }
4. 绘制。绘制比较麻烦,一开始我是所有元素分别绘制的,发现闪烁明显,后来采用绘制到内存图像,再把图像绘制到界面的方式,即双缓冲的思路。具体方式是这样:
1). 创建内容图像

// 准备图形 Bitmap itemImage = new Bitmap(e.Bounds.Width, e.Bounds.Height, e.Graphics); var itemGraphics = Graphics.FromImage(itemImage); itemGraphics.CompositingQuality = CompositingQuality.HighQuality; itemGraphics.SmoothingMode = SmoothingMode.AntiAlias; var itemRect = new Rectangle(0, 0, e.Bounds.Width, e.Bounds.Height);
2). 绘制背景

// 绘制背景 var bgBrush = new SolidBrush(Color.White); itemGraphics.FillRectangle(bgBrush, itemRect); bgBrush.Dispose();
3). 绘制头像,获取标题第一个字,将其绘制到头像圆形中

// 绘制头像圆形 var headImageBrush = new SolidBrush(Color.DarkTurquoise); if (isSelected) { headImageBrush.Color = Color.Tomato; } var headImageRect = new Rectangle(new Point(itemRect.X + this.ItemPadding, itemRect.Y + this.ItemPadding), this.HeadshotSize); itemGraphics.FillEllipse(headImageBrush, headImageRect); headImageBrush.Dispose(); // 绘制头像中的字符 string hsLetter = title.Substring(0, 1); // 取标题第一个字符作为头像字显示 var letterBrush = new SolidBrush(Color.White); var letterSF = new StringFormat(StringFormatFlags.NoWrap); letterSF.Alignment = StringAlignment.Center; letterSF.LineAlignment = StringAlignment.Center; var letterRect = headImageRect; itemGraphics.DrawString(hsLetter, this.FontForHeadshot, letterBrush, letterRect, letterSF); letterBrush.Dispose(); letterSF.Dispose();
4). 绘制标题和描述文字绘制描述文字

//draw title var titleString = title; var titleBrush = new SolidBrush(Color.FromKnownColor(KnownColor.WindowText)); var titleRect = new Rectangle(itemRect.X + this.ItemPadding + this.HeadshotSize.Width + this.ItemPadding, itemRect.Y + this.ItemPadding, itemRect.Width - (itemRect.X + this.ItemPadding + this.HeadshotSize.Width + this.ItemPadding), this.TextPadding + this.MeasureTitleHeight + this.TextPadding); var titleSF = new StringFormat(StringFormatFlags.NoWrap); titleSF.Alignment = StringAlignment.Near; titleSF.LineAlignment = StringAlignment.Center; itemGraphics.DrawString(titleString, this.FontForTitle, titleBrush, titleRect, titleSF); titleBrush.Dispose(); titleSF.Dispose(); //draw keyword string var kwString = keyword; var kwBrush = new SolidBrush(Color.FromKnownColor(KnownColor.GrayText)); var kwRect = new Rectangle(itemRect.X + this.ItemPadding + this.HeadshotSize.Width + this.ItemPadding, itemRect.Y + this.ItemPadding + this.TextPadding * 2 + this.MeasureTitleHeight, itemRect.Width - (itemRect.X + this.ItemPadding + this.HeadshotSize.Width + this.ItemPadding), this.TextPadding * 2 + this.MeasureDescriptionHeight); var kwSF = new StringFormat(StringFormatFlags.NoWrap); kwSF.Alignment = StringAlignment.Near; kwSF.LineAlignment = StringAlignment.Center; itemGraphics.DrawString(kwString, this.FontForDescription, kwBrush, kwRect, kwSF); kwBrush.Dispose(); kwSF.Dispose();
5). 把图像绘制到界面

var image = OwnDrawItemToImage(e); if(image !=null) { e.Graphics.DrawImageUnscaled(image, e.Bounds); image.Dispose(); }
至此,界面绘制完成,这种方式可以避免一个 Item 被直接在界面绘制多次,造成效率低下和闪烁。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构