博主头像
憨憨Yo酱の博客

Ciallo ~

Password Master:用实体 GPG 密钥派生“无保存”的可复现密码 — 项目介绍与开发日志

导读

我发起 Password Master 的初衷是:怎么做一个不保存密码本身但能“管理”密码的工具?传统密码管理器把密码存下来,一旦出现严重漏洞就可能被窃取;本地自建也存在被备份/泄露的风险。Password Master 的思路是:不存密码,只用实体私钥 + 少量可复现输入派生密码。本文是项目的故事、设计思路、当前实现、已知问题与未来路线的草稿,供你对我的思路进行参考。

仓库(源码、Issue、贡献指南):https://github.com/biliyoyo520/paasword/
博客(我的个人博客):https://blog.yoyo250.fun/


背景与动机

最近一段时间看到一些主流密码管理器曝出高危漏洞,让我意识到“把所有密码存起来”的模式有根本性的风险。有人推荐搭建本地密码库,这固然更可控,但仍然要把密码写入磁盘或备份设备——这与项目追求的“最小暴露面”目标不太一致。

于是我开始设想一个不同的方向:如果不保存密码本身,而通过某种可靠的、只在本地物理存在的私钥与可复现的输入派生密码——能不能既满足密码管理需求,又显著降低被集中窃取的风险?

Password Master 就是基于这个想法的 PoC / 工具链。


设计概要(高层)

  • 不保存派生结果(也就是不保存最终密码)。
  • 使用实体 GPG 私钥(智能卡、USB 密钥等)完成对“挑战(challenge)”的签名,签名的主体(MPI / 大数)作为派生材料的一部分(经过 SHA256)。
  • 用户输入:域名(或 URL)用户名一个“简单密码”。但challenge文件里只写入 sha256(简单密码),避免明文在磁盘残留。
  • 使用 KDF(优先 Argon2id,回退 PBKDF2)结合上面得到的 pepper 派生最终密钥,再映射到用户设定的字符集得到可用于登录的密码。
  • 支持 --faked-system-time(伪造时间)以控制签名里的 created 时间,从而提高可复现性;脚本尝试两次签名并对 MPI 做校验,若不一致会给出提示或提供多个派生结果便于对比。
  • 账号簿(计划):只记录域名与用户名,不记录派生密码或用户简单密码;账号簿会用实体 GPG 密钥加密,仅在插入密钥并验证后可解密查看。

已实现的功能(当前 v0.9.x 状态)

  • 使用 GnuPG 做 detach-sign,两次签名并尝试解析签名包(gpg --list-packets --verbose)以提取 MPI(对 RSA 为 data: 段,对 ECDSA/EdDSA 为 r || s)。
  • 优先以 MPI 的 SHA256 作为 pepper;若无法解析 MPI,退回使用签名二进制的 SHA256(降级方案)。
  • KDF:优先调用 argon2-cffi(Argon2id),若不可用则退回 PBKDF2 (sha256)。
  • 支持自定义输出字符集(去掉易混淆字符 i I l o O,并可包含 !@#),并以 bit-level 映射生成最终密码。
  • 两次签名的时间一致性检测:脚本尝试从第一次签名解析 created 时间并在第二次签名时重用该时间;当 created 与期望的 fake_time 不符时,脚本会在终端以带框信息提示用户(便于排查 ±1s 问题)。
  • 保留临时签名文件以便用户手动调试(默认不自动删除)。
  • 在 Windows 上做过初步测试(包含 gpg 参数等号形式 --faked-system-time=... 的兼容处理)。

当前已知限制与风险(非常重要)

  • 需要预装 GnuPG:脚本依赖系统的 gpg(目前在 Windows + GnuPG 2.4.x 做过测试)。
  • 需要实体密钥或智能卡:私钥必须物理存在(例如 OpenPGP card、YubiKey、CanoKey 等),脚本本身不处理私钥的导入或裸密钥文件。
  • ±1 秒签名时间问题:某些环境下即使使用 --faked-system-time,签名的 created 字段仍可能和预期相差 1s 导致不一致。当前脚本的应对策略是两次签名并基于 MPI 进行判定,若 MPI 相同则继续;若不同则输出两个派生供对比。该问题在不同平台/版本上表现不同,还需要更多测试与改进。
  • 输出密码字符兼容性:生成的密码有可能(不)包含特殊字符(如 !@#),有些网站对密码字符集有限制或在某些情况下有额外复杂度要求,用户需自行调整字符集(OUTPUT_ALPHABET)或长度(DESIRED_CHARS)。
  • 未经过安全审计:目前代码仅为研究 / PoC 级别,未经第三方安全审计或渗透测试。所有安全性论断基于理论与有限测试,可能存在未知漏洞或边缘场景的失效。若发现安全问题,请提交 issue 或通过邮件私下报告(详见仓库联系方式)。
  • 跨设备/跨实现差异:不同 GnuPG 版本、不同智能卡固件或 libgcrypt 行为可能影响签名细节(尤其是非 RSA、历史性的 DSA 实现)。脚本在检测到 DSA 时会警告。

账号簿与 UX(未来计划)

我计划将项目扩展为一个更易用的工具(不是为了替代普通密码管理器,而是给重视威胁模型的用户提供另一个选项):

  • 账号簿(本地):仅保存域名与用户名,不保存派生密码或“简单密码”。账号簿文件可以用实体 GPG 密钥加密,只有插入对应私钥并通过 PIN/touch 验证后才能解密查看。可手动备份到 U 盘(便于离线保管)。
  • TUI(文本用户界面):使用轻量 TUI(终端界面)代替复杂 GUI,便于在各类系统终端上运行,并减少对 GUI 依赖。界面设计参考 Reticulum 项目中 NomadNet 的 TUI 风格,追求简洁与可键盘操作性。
  • 配置文件:保存常用设置(如默认字符集、DESIRED_CHARS、FAKE_TIME 模板、默认用户名等),便于跨设备迁移(配置文件可导出/加密备份)。
  • 发行包:把核心库整理为可安装的 PyPI 包,并根据平台生成独立发行包(Windows exe、macOS 包、Linux wheel),降低使用门槛。
  • 自动清理临时文件:默认保留临时签名文件用于调试,但未来加入可选设置,在安全模式下自动清除这些文件。

使用示例


\$ python password.py
gpg --card-status 部分输出(前几行):
Reader ...........: yubikey OpenPGP PIV OATH 0
Application ID ...: A000000308000010000100
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: Yubico
Serial number ....: 12345678
Name of cardholder: yoyo250
Language prefs ...: en
使用的 faked-system-time = 20260802T103000 (请求: 20260802T103000, 卡创建: 19000101T000000)
请输入用户名(login):yoyo250
请输入站点域名或 URL:https://blog.yoyo250.fun/
请输入你的简单密码(回车后不回显):
请再次输入你的简单密码(回车后不回显):
进行第 1 次签名(可能需要 PIN / touch):
gpg: WARNING: running with faked system time: 2026-08-02 10:30:00

第二次签名强制使用相同的 faked-system-time = 20260802T103000
进行第 2 次签名(可能需要 PIN / touch):
gpg: WARNING: running with faked system time: 2026-08-02 10:30:00


==== 时间不符警告(sig1.bin) ====
┌────────────────────────────────────────────┐
│ sig1.bin created时间戳: 1785666601 │
│ UTC时间: 2026-08-02 10:30:01 │
│ fake_time: 20260802T103000 │
└────────────────────────────────────────────┘

【注意】签名时间与 fake_time 不符,可能导致密码不一致。请检查时间设置或重试。

MPI1 SHA256=765a21*6e20ab
MPI2 SHA256=6776f7*83cd29
警告:两次签名的 MPI 不一致!将分别派生两个密码以便对比。

=== sig1.bin 信息与派生密码===
sig1.bin created时间戳: 1785666601
UTC时间: 2026-08-02 10:30:01
sig1.bin hash:417cda*2c0d9f
sig1.bin 派生密码:tAu@ZUt0nM2pn2m7aUS5

=== 【时间正确】 sig2.bin 信息===
┌────────────────────────────────────┐
│ sig2.bin created时间戳: 1785666600 │
│ UTC时间: 2026-08-02 10:30:00       │
│ fake_time: 20260802T103000         │
└────────────────────────────────────┘
┌────────────────────────────────────────────────────┐
│ Done!                                              │
│ Hash结果:                                          │
│       e252f8*e5e550a                               │
│                                                    │
│ 最终密码:                                          │
│       0Qn0WmQhbvMQ0edd!sgW                         │
│                                                    │
│ 提示:复制完整的一整行密码(避免只取末尾部分)         │
└────────────────────────────────────────────────────┘

脚本完成。若要保留或删除临时签名文件,请查看: gpgderive_***
提示:本脚本使用 MPI 派生模式;只要 key 与输入(域名、用户名、口令)一致,结果在不同设备/时间应保持稳定。
按下 <Enter> 退出

安全提示与联系方式

  • 强烈建议:把私钥保存在受信硬件(智能卡、受保护的 USB Key),不要把私钥导出到不受信的设备。
  • 不要 在不受信环境(公共电脑、未加固的虚拟机等)输入你的“简单密码”。
  • 本项目还未通过第三方安全审计;若你发现漏洞或对安全性有严肃顾虑,请通过 GitHub issue 或邮箱与我联系(在仓库中查看联系方式)。
  • 代码许可与贡献方式见仓库 README。

我需要的帮助 / 如何参与

如果你对这类“无保存派生”思路感兴趣,欢迎贡献或帮助测试。尤其需要的帮助包括:

  • 在更多平台与 GnuPG 版本上测试(Windows、Linux、macOS;GnuPG 2.4.x / 2.3.x / 2.2.x 等),记录签名 created 字段与 MPI 行为差异。
  • 帮助设计并实现 TUI,或把核心派生逻辑封装为可重用的库(便于后续打包)。
  • 进行安全审计或给出改进建议(比如如何更好地减少临时文件影响、如何在移动/Android 上安全地与实体密钥交互)。
  • 文档、国际化、或为不同网站生成密码兼容性预设(例如“去掉特殊字符版本”模板)。

致谢与版权

项目源代码与 issue 列在 GitHub(https://github.com/biliyoyo520/paasword/)。如果你在使用中有发现问题、想法或者贡献,欢迎提交 PR 或 Issue。本文稿授权你在 Typecho 博客上编辑发布,若需我进一步把草稿渲染成适配 Typecho 的 HTML/Markdown(含 front-matter),我可以继续协助。


免责声明:本文与工具并不是正式的企业级密码管理产品;本项目目前还未发布正式版,使用前请确保派生密码可复现,谨防丢失密码风险;在将其用于真实高风险场景前,请务必进行详尽的安全评估与审计。

Password Master:用实体 GPG 密钥派生“无保存”的可复现密码 — 项目介绍与开发日志
https://blog.yoyo250.fun/archives/coding/16.html
本文作者 yoyo
发布时间 2025-09-07
许可协议 CC BY-NC-SA 4.0
发表新评论