MD-Vault 架构思考:为纯文本极客打造的零依赖本地优先 2FA 密码看板
MD-Vault 架构思考:为纯文本极客打造的零依赖本地优先 2FA 密码看板
很长一段时间里,我都习惯直接在文本文件里记录日常账号和密码——它最轻便、最透明,也永远不会被任何软件格式所绑定。
但随着积累的账号越来越多,查找账号密码成了一件挺麻烦的事;更让人头疼的是,在日常复制粘贴时,有时还会手误改动甚至删除了密码,导致下次登录时莫名碰壁。
为了解决这个真实又琐碎的日常痛点,我让 AI 帮我做了一个本地的密码管理小应用——这便是 MD-Vault 的真实起点。在后续的打磨中,我们进一步让纯文本兼具了直观看板交互、一键复制防误触,以及基于浏览器原生 WebCrypto 实现毫秒级的离线 2FA/TOTP 动态口令计算。
1. 核心设计:用单文件 HTML 查看本地 Markdown
很多密码工具的问题在于,一旦用了它,数据就被关进了专有的私有格式里。MD-Vault 则遵循一个原则:代码与数据完全分离。
- 密码始终保存在你的 Markdown 文档中:
真实的账号、密码与备用码依然记录在你本地磁盘的
.md文件里。你随时可以用任何文本编辑器(VS Code、Obsidian、Vim 甚至系统自带记事本)直接查阅和编辑,没有任何私有格式绑定。 - 通过
index.html本地加载与展示:index.html本身只是一个不到 30KB 的纯静态单文件应用,里面不包含任何用户的密码数据。日常使用非常简单:- 双击用任意现代浏览器打开
index.html; - 点击右上角选择自己的密码文件,或者直接把
.md文件拖拽扔进浏览器窗口; - 浏览器在本地内存中瞬时完成解析,生成清晰规整的响应式卡片。
- 双击用任意现代浏览器打开
- 纯本地运行,零网络依赖: 所有解析、搜索过滤以及一键复制防手误功能,全部在浏览器的本地内存中完成。全程无需启动后端服务,也不发任何外部网络请求,即使电脑断网也能 100% 正常使用。
2. 动态验证码:在浏览器里用 WebCrypto 计算 TOTP
在用文本记录密码时,另一个不便之处在于两步验证(2FA/TOTP):以往每次遇到需要验证码的网站,都得停下手头的工作,拿出手机打开验证器应用查看 6 位动态码。
既然浏览器原生提供了强大的 WebCrypto 密码学接口,完全可以让页面在本地根据密钥和时间戳直接算出动态验证码。
在 Markdown 模板中,只需按照约定写下密钥:
- **GitHub**:
- username: `[email protected]`
- password: `my-super-secret-pass`
- otpauth: `otpauth://totp/GitHub:octocat?secret=JBSWY3DPEHPK3PXP&issuer=GitHub`
2.1 时间步进与 HMAC-SHA1 计算
标准 TOTP(RFC 6238)的核心是拿当前 Unix 时间戳除以 30 秒,得到当前的时间步(Time Step):
随后将时间步长转换为 8 字节大端序计数器,与解码后的 Base32 密钥进行 HMAC-SHA1 运算:
2.2 动态截断与 6 位数字代码提取
从 20 字节的哈希结果中取最后一个字节的低 4 位作为动态偏移量(Offset):
提取从该偏移量起始的 4 个字节,抹去最高符号位并取模得到标准 6 位数字:
浏览器端的具体实现非常精炼:
// 基于原生 WebCrypto 的 TOTP 计算函数
export async function generateTotp(secretBase32: string): Promise<{ code: string; remainingSeconds: number }> {
const epoch = Math.floor(Date.now() / 1000);
const timeStep = Math.floor(epoch / 30);
const remainingSeconds = 30 - (epoch % 30);
const keyBuffer = base32ToBuffer(secretBase32);
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
keyBuffer,
{ name: 'HMAC', hash: { name: 'SHA-1' } },
false,
['sign']
);
const timeBuffer = new ArrayBuffer(8);
const dataView = new DataView(timeBuffer);
dataView.setUint32(4, timeStep, false);
const hmacSignature = await window.crypto.subtle.sign('HMAC', cryptoKey, timeBuffer);
const hash = new Uint8Array(hmacSignature);
const offset = hash[hash.length - 1] & 0x0f;
const binary =
((hash[offset] & 0x7f) << 24) |
((hash[offset + 1] & 0xff) << 16) |
((hash[offset + 2] & 0xff) << 8) |
(hash[offset + 3] & 0xff);
const otp = binary % 1000000;
return {
code: otp.toString().padStart(6, '0'),
remainingSeconds
};
}
页面以 1 秒为周期刷新倒计时,剩余时间不足 5 秒时平缓变色提醒,点击即可直接复制当前的 6 位动态验证码。
3. 本地安全与日常使用建议
为了让日常使用既顺手又安心,MD-Vault 在设计上遵循了几个简单的原则:
- 零外部网络请求:整个页面的样式、图标和逻辑全部内嵌在单个 HTML 文件中,不引入任何外部 CDN 资源、统计脚本或远程字体,断网也能正常运转;
- 配合系统级加密更放心:因为凭据本身是普通的 Markdown 文本,如果担心电脑上的明文存储风险,可以随手将密码文件放在系统的加密磁盘映像、BitLocker / FileVault 目录中,或者使用
git-crypt进行透明加密; - 纯粹的本地开源工具:MD-Vault 是一个完全免费、轻量的本地工具,数据始终在您自己手里,永远不会有账号注册或云端同步的打扰。
4. 留给未来的想法(AI 的建议清单)
在完成当前这个轻量版本后,AI 在代码之余,还帮我构思了一份挺有意思的“特性拓展建议”。虽然为了保持目前的极简小巧暂时没有做,但我把它们记录在了项目的待办清单中,留待日后有实际需要时再慢慢拓展:
- 界面防窥遮挡:在办公室或投屏共享屏幕时,给密码加一层模糊滤镜,鼠标悬停时才显影,或者直接一键盲拷,防止被旁人无意看到;
- 剪贴板定时清空:复制密码后开启 30 秒倒计时,超时自动清空剪贴板,避免密码长时间留在输入法或剪贴板历史记录里;
- 页面内直接编辑回写:利用现代浏览器的
File System Access API,允许直接在网页上新增或修改卡片,并自动保存回本地的.md文件,省去切回文本编辑器的步骤; - 单文件密码自解密:探索用 WebCrypto 的
AES-GCM算法,在打开页面时输入主密码,只在内存中解密渲染,本地磁盘上只存加密后的密文文件; - 终端或快捷启动器扩展:写一个简单的 Raycast 插件或命令行脚本,共用同一份 Markdown 文件,在终端敲一行命令直接把 2FA 动态码拷进剪贴板。
5. 在线体验与 GitHub 仓库
- 在线演示 (GitHub Pages):https://xiangc579.github.io/md-vault/
- GitHub 开源仓库:https://github.com/xiangc579/md-vault
纯文本极客的零依赖本地优先 2FA 密码看板
厌倦了中心化密码库被拖库撞库?体验高密度纯文本 Markdown 凭据管理,原生浏览器 WebCrypto 实时计算 2FA。