
原生 Windows 本地加密密钥生成与管理工具
开发过程中,JWT Secret、Session Secret、加密密钥和随机密码往往散落在笔记、聊天记录或临时 .env 文件里。它们一旦泄露,排查成本极高;而手工生成又难以保证随机性、统一命名和轮换记录。KeyForge 把“生成—加密保存—项目归类—合并导出—备份”这条链路收进一个离线单文件 Windows 程序,不依赖浏览器、Node.js 或网络。
它采用 C# WinForms 自绘界面,使用 PBKDF2-SHA256 派生密钥、AES-256-CBC + HMAC-SHA256 加密保险库,并可通过 P/Invoke 调用 Windows 原生 WebAuthn 实现 FIDO 安全密钥登录。所有数据保存在 EXE 同目录的 KeyForgeData 文件夹中,整体迁移只需拷贝该目录。
项目定位与设计取舍
KeyForge 面向需要在本地安全生成和管理开发密钥的 Windows 开发者与运维人员。它解决的问题很具体:敏感值不应以明文形式长期停留在文本文件或聊天记录中,同时又要能快速取用、按项目归类、按环境导出。
几个明确的设计取舍:
- 离线单文件:不联网、不依赖外部运行时,降低攻击面,也便于在隔离环境使用。
- 本地加密保险库:所有密钥序列化后整体加密写入
vault.dat,磁盘上不存在明文密钥。 - 双登录方式:主密码是基础;FIDO 安全密钥作为可选增强,通过 Windows Hello / FIDO2 完成用户验证。
- 项目化管理:密钥按“项目标识_环境_类型”自动命名,重名时自动追加序号,避免导出
.env时键冲突。
主密码无法找回。忘记主密码且没有可用的 FIDO 密钥时,vault.dat 中的数据不可恢复。请在首次创建后立即做一次加密备份。


整体架构
项目是分层式单体桌面应用,UI 层、业务逻辑层与安全存储层通过静态工具类和存储类解耦,没有引入依赖注入或第三方框架。
| 层次 | 主要类型 | 职责 |
|---|---|---|
| UI 层 | AuthForm、MainForm、GeneratorDialog、MergeDialog、SettingsDialog |
窗口、对话框与主交互 |
| 控件与主题层 | Controls、Theme、KeyCardControl、ConfirmDialog |
圆角面板、按钮、输入框等自绘控件 |
| 业务逻辑层 | Models、Generators、Naming、TimeUtil |
数据模型、CSPRNG 生成、自动命名、时间格式 |
| 安全与存储层 | Security、FidoAuthenticator、FidoStore、VaultStore |
加解密、完整性校验、FIDO 凭据与解锁资料 |
数据流是单向的:UI 层只通过 VaultStore 读写保险库、通过 FidoStore 管理 FIDO 凭据;Security 负责密钥派生、加解密与 MAC 校验;Generators 只负责产生随机值,不接触存储。FIDO 认证通过 P/Invoke 直接调用 webauthn.dll,不经过浏览器。
核心运行链路
从启动到保存一条密钥,完整链路如下:
Program创建数据目录,打开AuthForm。- 用户输入主密码或使用 FIDO 解锁,
VaultStore.Unlock派生密钥、解密并校验vault.dat。 MainForm加载项目与密钥列表,启动 30 分钟闲置锁定计时。GeneratorDialog选择类型、项目、环境,Generators用 CSPRNG 生成值并预览。- 保存时
VaultStore将新密钥加入VaultData,Security重新加密写回vault.dat,并保留.bak。
保险库的加密格式
vault.dat 是一个 JSON 信封,包含 KDF 参数、IV、MAC 和密文载荷。VaultEnvelope 的字段固定为 Format、Version、Kdf、Cipher、Mac、Payload,格式标识为 keyforge-vault,版本为 1。
派生与加密参数:
| 参数 | 取值 | 说明 |
|---|---|---|
| KDF | PBKDF2-SHA256 | 名称写入信封,解锁时校验 |
| 迭代次数 | 210000(默认) | 解锁时接受 10000–2000000 区间 |
| 盐长度 | 16 字节 | 每次创建/改密重新生成 |
| 派生材料 | 64 字节 | 前 32 字节为 AES 密钥,后 32 字节为 HMAC 密钥 |
| 加密算法 | AES-256-CBC + PKCS7 | 每次保存生成新 IV |
| 完整性 | HMAC-SHA256 | 覆盖信封元数据与密文,常量时间比较 |
解锁时先校验信封格式与加密参数,再用派生出的 MAC 密钥重算 HMAC,比较通过后才解密。MAC 校验失败与解密失败返回同一条错误信息,避免区分“密码错误”和“文件损坏”泄露信息。
byte[] material = DeriveMaterial(password, salt, envelope.Kdf.Iterations);
byte[] encryptionKey = Slice(material, 0, 32);
byte[] macKey = Slice(material, 32, 32);
Zero(material);
byte[] actualMac = ComputeMac(macKey, envelope, salt, iv, payload);
if (!ConstantTimeEquals(actualMac, expectedMac))
{
Zero(encryptionKey);
Zero(macKey);
throw new VaultException("主密码错误,或保险库文件已损坏。");
}
保存时先序列化 VaultData,加密后组装信封、计算 MAC,再通过原子写入落盘。VaultStore 在构造时确定 vault.dat 与 vault.dat.bak 路径,写入过程保留备份文件,便于在异常中断后恢复。
修改主密码会生成新的盐和新的派生材料,并用新密钥整体重加密。旧密钥在保存成功后清零;保存失败则回滚到旧密钥,避免保险库处于半更新状态。
FIDO 登录的实现
FIDO 相关逻辑分两部分:FidoAuthenticator 负责调用系统 API,FidoStore 负责凭据元数据与解锁资料。
注册流程调用 WebAuthNAuthenticatorMakeCredential,构造 webauthn.create 的 clientData,challenge 为 32 字节随机值,RP ID 默认为 keyforge,origin 为 local://keyforge。算法参数同时提供 ES256(-7)与 RS256(-257)。注册成功后从返回的 attestation 中取出凭据 ID,交给 FidoStore.AddCredential 保存。
验证流程调用 WebAuthNAuthenticatorGetAssertion,构造 webauthn.get 的 clientData,并通过 allow list 限定可用凭据。返回后校验凭据 ID 必须在允许列表中,再校验认证器数据:
private static void VerifyAuthenticatorData(byte[] authenticatorData, string rpId)
{
if (authenticatorData == null || authenticatorData.Length < 37)
{
throw new FidoException("FIDO 返回的认证器数据不完整。");
}
byte[] expectedRpHash;
using (SHA256 sha = SHA256.Create())
{
expectedRpHash = sha.ComputeHash(Encoding.UTF8.GetBytes(rpId));
}
for (int index = 0; index < 32; index++)
{
if (authenticatorData[index] != expectedRpHash[index])
{
throw new FidoException("FIDO 凭据与当前应用标识不匹配。");
}
}
byte flags = authenticatorData[32];
if ((flags & 0x01) == 0)
{
throw new FidoException("FIDO 验证没有检测到用户存在(UP 标志缺失)。");
}
}
前 32 字节是 RP ID 的 SHA-256,必须与当前应用标识一致;第 33 字节的 UP 标志必须置位,确保检测到用户存在。验证通过后调用 MarkUsed 更新最近使用时间。
FIDO 解锁资料的保护
FIDO 登录之所以能直接解锁保险库,是因为主密码被 DPAPI 保护后存于 fido-unlock.bin。FidoStore.ProtectPassword 使用 ProtectedData.Protect,作用域为 CurrentUser,附加熵为固定字节串 KeyForge.FIDO.Unlock.v1。解锁时 UnprotectPassword 还原主密码,再走正常的 VaultStore.Unlock 流程。
凭据元数据保存在 fido.json,包含 Version、RpId、UserId 和 Credentials 列表;每个凭据记录 Id、Name、CreatedAt、LastUsedAt。UserId 是 24 字节随机值的 Base64,首次注册时生成并持久化。两个文件都通过临时文件 + 替换的方式原子写入。
fido-unlock.bin 由当前 Windows 用户的 DPAPI 保护。把整个 KeyForgeData 迁移到另一台电脑或另一个 Windows 账户后,FIDO 解锁通常失效,需要用主密码解锁,然后在设置中重新添加 FIDO 密钥。
密钥生成与命名
Generators 使用 RandomNumberGenerator(CSPRNG)产生随机值,不使用 Random。生成类型与参数:
| 类型 | 可选参数 | 说明 |
|---|---|---|
| 加密密钥 | 128 / 192 / 256 bit;Base64URL / Base64 / Hex | 按位长生成随机字节后编码 |
| JWT Secret | 256 / 384 / 512 bit | 同上 |
| Session Secret | 256 / 384 / 512 bit | 同上 |
| 随机密码 | 8–128 位;大小写、数字、符号;排除易混淆字符 | 按字符集抽样 |
GeneratorDialog 在打开时即生成一次预览,切换类型或参数后重新生成。保存时按“项目标识_环境_类型”自动命名,若已存在同名密钥则追加序号。名称、备注、项目、环境随密钥一起写入 VaultData。
主窗体与自动锁定
MainForm 采用左侧项目列表 + 右侧密钥卡片的两栏布局,顶部有搜索框,支持 Ctrl+F 聚焦搜索、Ctrl+N 打开生成对话框、Esc 清空搜索。密钥卡片支持显示/隐藏、复制、轮换、编辑、删除。
自动锁定由 Timer 驱动,间隔 15 秒检查一次,结合 ActivityMonitor.Touch() 记录的最近活动时间,超过 30 分钟无操作即触发锁定。窗体关闭时若并非因锁定而关闭,会调用 _store.Lock() 清零内存中的密钥材料。
构建与运行
项目使用 build.ps1 调用 csc.exe 编译,源码按 C# 5 语法(/langversion:5)编写,因此示例代码不使用现代 C# 特性。
# 在仓库根目录执行
powershell -ExecutionPolicy Bypass -File .\build.ps1
构建脚本引用 app.manifest 与 KeyForge.ico。仓库中若缺少这两个文件,需要先确认它们存在,或由 tools/make-icon.ps1 生成图标后再构建。
运行环境要求:
| 项目 | 要求 |
|---|---|
| 操作系统 | Windows 10 1903+ 或 Windows 11 |
| 运行时 | .NET Framework 4.x |
| FIDO 功能 | 需要 webauthn.dll,旧版 Windows 不可用 |
| 数据目录 | EXE 同目录下的 KeyForgeData |
KeyForgeData 目录中的文件:
| 文件 | 内容 |
|---|---|
vault.dat |
加密保险库信封(JSON) |
vault.dat.bak |
保存时保留的备份 |
fido.json |
FIDO 凭据元数据 |
fido-unlock.bin |
DPAPI 保护的主密码 |
使用方法
首次创建保险库
- 启动
KeyForge.exe,在认证窗体选择创建主密码。 - 输入至少 10 个字符、不超过 256 个字符的主密码。
- 进入主窗体后,建议立即在“设置”中下载一次加密备份,并妥善保存。
生成并保存密钥
- 点击左侧“生成密钥”或按
Ctrl+N。 - 选择类型(加密密钥 / JWT Secret / Session Secret / 随机密码)。
- 选择项目与环境,确认自动命名或手动修改名称。
- 调整位长、编码或密码字符集,预览确认后保存。
合并导出 .env
在“合并导出”对话框勾选多个项目和目标环境,工具会按命名规则汇总键值并生成标准 .env 内容,可直接复制。导出内容来自已解锁的保险库,不会写入磁盘明文。
配置 FIDO 登录
- 使用主密码解锁后进入“设置”。
- 添加 FIDO 安全密钥,按系统提示完成 Windows Hello 或安全密钥验证。
- 注册成功后,主密码会被 DPAPI 保护写入
fido-unlock.bin,下次可直接用 FIDO 解锁。
迁移与备份
整体迁移时,退出程序后拷贝 EXE 与整个 KeyForgeData 文件夹到目标机器。目标机器需使用相同 Windows 账户才能继续使用 FIDO 解锁;否则用主密码解锁后重新绑定 FIDO。
常见问题与排错
提示“主密码错误,或保险库文件已损坏”:先确认大小写与输入法;若确认密码正确,检查 vault.dat 是否被截断。可尝试用 vault.dat.bak 替换后重试。
FIDO 不可用:确认系统为 Windows 10 1903+ 或 Windows 11,且 webauthn.dll 可加载。FidoAuthenticator.IsAvailable 通过 WebAuthNGetApiVersionNumber() >= 1 判断。
FIDO 解锁资料缺失或无法读取:fido-unlock.bin 由当前用户 DPAPI 保护,换用户或换机后通常失效。用主密码解锁,在设置中移除旧凭据并重新添加。
构建失败提示缺少文件:检查 app.manifest 与 KeyForge.ico 是否存在,必要时先运行 tools/make-icon.ps1。
忘记主密码:主密码无法找回。若仍有可用的 FIDO 密钥,可通过 FIDO 解锁后在设置中修改主密码;否则数据不可恢复。
结论
- 保险库的安全性建立在 PBKDF2-SHA256(210000 次迭代)+ AES-256-CBC + HMAC-SHA256 之上,MAC 校验先于解密,且使用常量时间比较。
- FIDO 登录不是独立密钥体系,而是通过 DPAPI 保护主密码实现“无密码输入解锁”,因此迁移后需要重新绑定。
- 所有密钥生成走 CSPRNG,命名规则保证
.env导出时键不冲突。 - 首次使用后立即做加密备份,并确认目标机器的 Windows 账户与 FIDO 绑定关系。
- 构建前确认
app.manifest与KeyForge.ico存在,源码保持 C# 5 语法兼容。
本文基于 KEY (KeyForge) 仓库当前实现整理。代码与配置以仓库为准;版本变化后请以实际源码为准。
主题测试文章,只做测试使用。发布者:zy,转转请注明出处:https://zynetc.top/?p=2026102731