用记忆保护你的私钥

归墨 (Guī Mò) 意即 “记忆归于墨迹,墨迹化为密钥。” 首字母恰好对应 Generate Mnemonic。愿你的记忆像墨一样,落笔即定,永不褪色。

演示gm.zlog.in | 源码github.com/zlog-in/gm

引子:私钥的困境

区块链世界有一个根本性矛盾——你的全部资产由一串私钥控制,而这串私钥你既不能忘记,也不能让别人知道。

常见的保管方式各有硬伤:

  • 抄在纸上:纸会丢、会被发现、会被水火损毁。
  • 存在硬件钱包里:设备本身也会坏、会丢、会被盗。
  • 加密后存云端:你需要记住加密密码——问题又回到了原点。
  • 第三方托管:放弃了区块链最核心的价值,依赖信任。

有没有一种方式,让私钥既不存在于任何物理介质上,又能在需要时被可靠地还原?

GM 的思路是:把你的记忆本身变成私钥的来源。


核心思想:从"记住密钥"到"记住素材"

GM 不生成随机助记词,而是把你提供的素材——一段文字、一个文件、或两者的组合——通过确定性的密码学流水线,转化为标准的 BIP39 助记词。

关键词是确定性:同样的输入,永远产出同样的助记词。这意味着你不需要保存任何东西,只需要记住你输入了什么。

一段对你有特殊意义的文字、一张特定的照片、一首歌的音频文件——这些东西天然地刻在你的长期记忆里。GM 把这种"人对素材的记忆"转化为"对密钥的掌控"。


完整流水线:从输入到助记词

整个过程分三步,每一步都使用公开的、经过广泛审计的标准算法。

第一步:输入编码

文本(任何语言)──► TextEncoder(UTF-8 编码)──────┐
                                               ├──► inputBytes
文件(任何格式)──► FileReader(原始二进制)  ──────┘

文本通过 UTF-8 编码转为字节,文件直接读取原始二进制。如果两者都提供,按文本在前、文件在后的固定顺序拼接。

这个顺序是协议的一部分——“hello” + photo.jpg 和 photo.jpg + “hello” 会产出完全不同的助记词。用户需要记住的不仅是素材内容,还有输入方式。

第二步:密钥派生(PBKDF2-SHA256)

这是整个流程的核心环节。

inputBytes ──► PBKDF2-SHA256 ──► entropy(128 或 256 bit)

参数:
  哈希函数:SHA-256
  盐值(Salt):空
  迭代次数:默认 1,000,000(可配置)
  输出长度:128 bit(12 个词)或 256 bit(24 个词)

PBKDF2(Password-Based Key Derivation Function 2)是 NIST 标准化的密钥派生函数(SP 800-132)。它的工作原理是对输入反复执行 HMAC-SHA256 运算,每一轮的输出作为下一轮的输入。

为什么要做一百万轮?因为这是一种计算换安全的策略。假设攻击者想暴力猜测你的输入素材,每猜一次都要执行一百万轮 SHA-256。在现代 CPU 上,单次猜测大约需要数秒。如果你的素材有足够的不可预测性,攻击者面对的搜索空间将大到绝望。

为什么不加盐? 传统用法中,Salt 用于防止彩虹表攻击和区分不同用户的相同密码。但 GM 的场景不同——我们需要确定性:同样的输入必须永远产出同样的结果。如果加了 Salt,用户就得同时记住输入素材和 Salt,多了一个需要保管的秘密,与"只依赖记忆"的初衷矛盾。

这个取舍是可接受的,因为用户的输入素材(个人文字、个人文件)天然具有唯一性——它本身就起到了 Salt 的区分作用。

第三步:BIP39 助记词转换

PBKDF2 的输出是一串原始字节(entropy),接下来按照 BIP39 标准将其转化为人类可读的单词序列。

entropy(128 bit)
    │
    ├──► SHA-256(entropy) ──► 取前 4 bit 作为校验和
    │
    └──► 128 bit + 4 bit = 132 bit
              │
              ▼
         每 11 bit 一组 → 12 个索引(0~2047)→ 12 个单词

以 12 词为例:

  1. 计算校验和:对 entropy 做一次 SHA-256 哈希,取结果的前 4 bit。
  2. 拼接:128 bit 熵 + 4 bit 校验和 = 132 bit。
  3. 分段:将 132 bit 按每 11 bit 切一刀,得到 12 个数字,每个范围 0~2047。
  4. 查表:每个数字在 BIP39 词表(2048 个词)中找到对应的单词。

24 词的逻辑完全相同,只是 entropy 是 256 bit,校验和取 8 bit,总共 264 bit,切成 24 段。

校验和的意义在于错误检测——如果用户抄错了一个词,校验和大概率对不上,钱包软件会拒绝这组助记词。


为什么这套方案能兼顾记忆性与安全性

记忆性:利用人脑的长期记忆机制

人类的长期记忆对以下几类信息有天然的持久保持力:

  • 有情感联结的内容:一段恋人之间的对话、一封重要的邮件。
  • 有感官锚定的内容:一张特定的照片、一首旋律。
  • 有叙事结构的内容:一个故事、一段经历的文字描述。
  • 有重复接触的内容:你反复翻看的一页书、一个经常使用的文件。

这些内容往往被存储在语义记忆情景记忆中,衰退极慢。与之对比,12 个随机英文单词(传统 BIP39 助记词)几乎没有语义关联,依赖的是机械记忆——这是人脑最不擅长的记忆方式。

GM 的设计把密钥的"记忆载体"从机械记忆转移到语义/情景记忆上。你不需要记住助记词本身,只需要记住"我用了什么素材"。

安全性:多层防线

第一层:素材的不可预测性。 攻击者必须猜到你输入了什么。一段私人文字的信息熵远高于一个常见密码。如果素材是一个文件(比如一张照片的原始字节),搜索空间更是天文数字。

第二层:计算成本。 即使攻击者缩小了猜测范围,每次验证都要执行百万轮 PBKDF2。假设攻击者有一个包含 100 万条候选输入的字典:

100 万条 × 每条约 3 秒(百万轮 PBKDF2)≈ 35 天(单核 CPU)

这还只是最乐观的估计——攻击者不太可能把你的素材放进字典。

第三层:离线运行。 GM 是一个纯本地的 HTML 文件,不发送任何网络请求。没有服务器端,没有数据传输,没有第三方能看到你的输入。用户可以在断网状态下使用,甚至可以在浏览器开发者工具的 Network 面板中亲自验证。

第四层:算法透明。 使用的每一个算法(PBKDF2、SHA-256、BIP39)都是公开标准,被全球密码学社区审计了数十年。实际的加密运算由浏览器原生的 Web Crypto API 执行——这是由浏览器厂商维护的 C/C++ 实现(Chromium 使用 BoringSSL,Firefox 使用 NSS),不是手写的 JavaScript。


一个具体的例子

假设你选择的素材是一句诗:“人生天地间 忽如远行客”。

  1. TextEncoder 将这 10 个汉字编码为 30 字节的 UTF-8 序列。
  2. 这 30 字节被送入 PBKDF2,经过一百万轮 SHA-256 迭代,输出 16 字节(128 bit)的 entropy。
  3. 对 entropy 做 SHA-256,取前 4 bit 作为校验和,拼接后得到 132 bit。
  4. 每 11 bit 映射一个词,最终得到 12 个 BIP39 单词。

只要你记得这句诗,在任何有浏览器的设备上打开 GM,输入它,就能还原出完全相同的助记词。

如果你把这句诗搭配一张特定的照片一起输入,安全性会进一步提升——攻击者不仅要猜到你用了哪句诗,还要找到那张完全相同的照片文件。


需要注意的边界

输入质量决定一切

GM 的安全性完全取决于输入素材的不可预测性。以下是反面教材:

  • “123456” → 几乎零安全性
  • “hello world” → 约等于零
  • 你的生日 → 极易被社会工程猜到

以下是合理的素材选择:

  • 一段只有你知道的个人经历描述(>50 字)
  • 一个私人文件(照片、录音、文档)
  • 两者的组合

字节级精确

确定性意味着对输入的字节级敏感

  • “Hello” 和 “hello” 是不同的输入(H 和 h 的 UTF-8 编码不同)
  • 末尾多一个空格就是不同的输入
  • 同一张照片如果被重新压缩或转码,文件字节会改变,产出不同的结果

使用文件作为素材时,必须确保是完全相同的文件——不是"看起来一样的图片",而是"字节完全一致的同一个文件"。

迭代次数是协议的一部分

如果你用 100 万轮生成了助记词,下次还原时也必须用 100 万轮。不同的迭代次数会产出不同的结果。这个参数和输入素材一样,是你需要记住的。

建议挑选一个对你有重大意义且不易被猜测的数字,或者默认使用 100 万轮


正确性验证

GM 在每次页面加载时会自动运行 6 项密码学验证测试:

BIP39 官方测试向量(4 项):使用 Trezor 仓库中的标准测试数据,验证从 entropy 到助记词的完整转换链路。例如,全零的 entropy 00000000000000000000000000000000 必须产出 “abandon abandon abandon … about”。

PBKDF2 交叉验证(2 项):使用 Python hashlib.pbkdf2_hmac 独立计算的参考值,验证浏览器的 PBKDF2 实现在相同参数下产出一致的结果。

核心验证方法论:不信任自己的实现,用两个独立的环境对同样的输入计算同样的结果,交叉比对。

打开浏览器开发者控制台(F12 → Console),你应该看到:

[GM] All 6 cryptographic tests passed
   PASS  BIP39 vector 1
   PASS  BIP39 vector 2
   PASS  BIP39 vector 3
   PASS  BIP39 vector 4
   PASS  PBKDF2 (hello, 1M iter, 128bit)
   PASS  PBKDF2 (hello, 1M iter, 256bit)

总结

GM 的本质是一个确定性密码学转换器

你的记忆(文字/文件)──► 标准算法 ──► BIP39 助记词

它不创造随机性,不存储任何东西,不连接任何网络。它只是一座桥——把人脑擅长记住的东西,转化为密码学上有效的密钥。

这座桥的可靠性建立在三个支点上:

  1. 算法是公开标准——PBKDF2、SHA-256、BIP39,不依赖任何私有实现。
  2. 运行环境是浏览器原生 API——加密运算由操作系统级的密码学库执行,不是手写代码。
  3. 确定性保证——相同输入、相同参数,在任何设备、任何时间,产出相同结果。

你要做的只有一件事:选择一个只有你能还原的输入,然后记住它。


免责声明

本工具为实验性项目,仅供研究和学习用途,按原样提供,未经独立安全审计。 请勿将其用于保护您无法承受损失的资产。作者不对因使用本工具而导致的任何损失承担责任。