KEY (KeyForge):原生 Windows 本地加密密钥生成与管理工具

开发过程中,JWT Secret、Session Secret、加密密钥和随机密码往往散落在笔记、聊天记录或临时 .env 文件里。它们一旦泄露,排查成本极高;而手工生成又难以保证随机性、统一命名和轮换记录。KeyForge 把“生成—加密保存—项目归类—合并导出—备份”这条链路收进一个离线单文件 Windows 程序,不依赖浏览器、Node.js 或网络。

KEY (KeyForge):原生 Windows 本地加密密钥生成与管理工具 封面
原生 Windows 本地加密密钥生成与管理工具的实现解析

原生 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 时键冲突。
KEY (KeyForge) 系统架构
KEY (KeyForge) 系统架构
从启动到生成并保存一条密钥
从启动到生成并保存一条密钥

整体架构

项目是分层式单体桌面应用,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,不经过浏览器。

核心运行链路

从启动到保存一条密钥,完整链路如下:

  1. Program 创建数据目录,打开 AuthForm。
  2. 用户输入主密码或使用 FIDO 解锁,VaultStore.Unlock 派生密钥、解密并校验 vault.dat。
  3. MainForm 加载项目与密钥列表,启动 30 分钟闲置锁定计时。
  4. GeneratorDialog 选择类型、项目、环境,Generators 用 CSPRNG 生成值并预览。
  5. 保存时 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,首次注册时生成并持久化。两个文件都通过临时文件 + 替换的方式原子写入。

密钥生成与命名

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 保护的主密码

使用方法

首次创建保险库

  1. 启动 KeyForge.exe,在认证窗体选择创建主密码。
  2. 输入至少 10 个字符、不超过 256 个字符的主密码。
  3. 进入主窗体后,建议立即在“设置”中下载一次加密备份,并妥善保存。

生成并保存密钥

  1. 点击左侧“生成密钥”或按 Ctrl+N。
  2. 选择类型(加密密钥 / JWT Secret / Session Secret / 随机密码)。
  3. 选择项目与环境,确认自动命名或手动修改名称。
  4. 调整位长、编码或密码字符集,预览确认后保存。

合并导出 .env

在“合并导出”对话框勾选多个项目和目标环境,工具会按命名规则汇总键值并生成标准 .env 内容,可直接复制。导出内容来自已解锁的保险库,不会写入磁盘明文。

配置 FIDO 登录

  1. 使用主密码解锁后进入“设置”。
  2. 添加 FIDO 安全密钥,按系统提示完成 Windows Hello 或安全密钥验证。
  3. 注册成功后,主密码会被 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

赞 (0)
zy的头像zy
VRCLOC:在 VRChat 里点亮同城玩家的中国地图
上一篇 6天前
文枢DocSmith:把本地代码仓库变成可发布的 WordPress 技术文档
下一篇 5天前

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注