首页
/ Bitcoin Core 钱包私钥加密机制:从 0.4.0 版发布说明到当前源码实现全解析

Bitcoin Core 钱包私钥加密机制:从 0.4.0 版发布说明到当前源码实现全解析

2026-09-06 17:35:03作者:裴麒琰

Bitcoin Core 0.4.0 版(2010 年)引入的原生钱包私钥加密是比特币客户端安全史上第一个里程碑式特性:它允许用户为钱包设置口令(passphrase),在发送比特币之前必须先解锁钱包。本文以 0.4.0 版发布说明 为核心,完整梳理该版本的主特性、已知兼容性问题与修复项,并对照当前仓库中的 加密上下文实现钱包加密主流程RPC 接口,说明这套"口令 → 主密钥 → 私钥"三层加密结构是如何一直沿用至今的。

一、0.4.0 版的核心发布内容

根据 release-notes-0.4.0.md,该版本的主特性是钱包私钥加密(Wallet Encryption):用户设置一句口令后,每次发送比特币前都必须输入该口令。发布说明给出了几条至今仍然成立的严肃警告:

  • 口令即所有权:忘记或丢失口令意味着丢失钱包内全部比特币,"没有任何人,包括 Bitcoin 开发者在内,能够帮你恢复";
  • 旧版客户端不兼容:早于 0.4 的 bitcoin 版本无法读取加密钱包,启动时直接崩溃;
  • 加密前务必备份:官方建议先关闭客户端、拷贝 wallet.dat 到安全位置,再执行加密,确认无误后删除该备份。

除了主特性,0.4.0 还包含两项值得记录的工程变更与修复:

变更项 说明
Berkeley DB 升级 从 bdb 4.7 升级到 bdb 4.8。文档明确警告:升级到 0.4 后若再回退到旧版本,可能因 bdb 4.7 无法读取 bdb 4.8 的 "log" 文件而无法启动。这意味着 0.4 是单向升级点,加密钱包 + 新版 BDB 共同锁死了回退路径
多线程死锁修复 修复了多个导致 bitcoin 无响应(becomes-unresponsive)的多线程死锁 bug
大交易数据库写入优化 针对输入(inputs)数量极多的交易优化数据库写入路径,文档将其定性为"修复了一个潜在的拒绝服务攻击"——即攻击者可构造大量输入的交易,使节点磁盘写入性能急剧劣化

发布说明还特别指出,钱包加密的技术细节参见源码树中的 doc/README(当前仓库中为 doc/README.md)。

二、加密的边界:只加密密钥,不加密整个钱包

0.4.0 发布说明中最关键的一段安全澄清是:

Bitcoin 内置的加密只加密发送比特币所需的实际密钥,而不是整个钱包。窃取钱包文件的人仍然能看到你所有的地址以及相关交易,你只是被保护免受他人花掉你的币。

这一点决定了钱包加密的真实安全语义:它防御的是"钱包文件被偷走后离线导出私钥、直接转走资金"这一场景,而不防御"交易历史与地址图谱泄露"。发布说明同时给出了完整的安全责任声明——一个稍具水平的钱包窃取木马只要再装一个键盘记录器,在你输入口令时把它记录下来,加密就形同虚设。官方列出的最低安全实践包括:

  1. 保持杀毒软件更新;
  2. 只在 Bitcoin 客户端内部输入钱包口令;
  3. 钱包口令不要与任何其他场景的口令相同(文档原话:"using the same passphrase only as your wallet passphrase")。

三、从发布说明到源码:三层密钥结构

0.4.0 文档描述的"口令加密钱包",在当前仓库中演化为一个三层结构,全部位于 src/wallet/ 目录下:

1. 口令 → 主密钥(CCrypter / CMasterKey)

crypter.h 的注释精确描述了这套算法(与 0.4 时代的 doc/README 一脉相承):

  • 钱包持有一个 CMasterKey,包含随机盐(salt)和被口令加密的随机密钥(vchCryptedKey);
  • 主密钥使用 AES-256-CBC 加密,加密所用的 key 由口令经 nDerivationMethod(0 == EVP_sha512())与 nDeriveIterations 轮迭代派生;
  • 私钥再用 AES-256-CBC 加密,密钥为主密钥本身,IV 取公钥的 double-SHA256 值(见 crypter.h L28-L31)。

crypter.h 中几个至今未变的常量:

inline constexpr unsigned int WALLET_CRYPTO_KEY_SIZE = 32;   // 256 位主密钥
inline constexpr unsigned int WALLET_CRYPTO_SALT_SIZE = 8;   // 8 字节盐
inline constexpr unsigned int WALLET_CRYPTO_IV_SIZE  = 16;   // AES-256-CBC 的 16 字节 IV

口令到密钥的派生函数 BytesToKeySHA512AES(实现于 crypter.cpp)默认执行 25000 轮 SHA-512 迭代(CMasterKey::DEFAULT_DERIVE_ITERATIONScrypter.h L48)。注释里保留着一段耐人寻味的历史:"25000 轮在一台 1.86 GHz Pentium M 上不到 0.1 秒,略低于我们愿意支持的最低端硬件"——这正是 2010 年前后的硬件画像,如今这一轮数已成为抗暴力破解的安全参数。

2. 主密钥 → 私钥(CScriptPubKeyManager)

每个私钥的加密点见 scriptpubkeyman.cpp

if (!EncryptSecret(master_key, secret, pubkey.GetHash(), crypted_secret)) {

即调用 crypter.cpp L111EncryptSecret,IV 参数传入 pubkey.GetHash()——与 crypter.h 头注释中"double-sha256 of the public key as the IV"的约定完全一致。密文随后以序列化形式写回钱包数据库,取代原来的明文私钥。

3. 加密主流程(CWallet::EncryptWallet)

wallet.cpp L843-L916EncryptWallet 完整实现了发布说明所暗示的事务性流程:

  1. 生成 32 字节随机主密钥与 8 字节随机盐(GetStrongRandBytes);
  2. 用口令加密主密钥,WriteMasterKey 写入数据库;
  3. WalletBatch 事务中逐个调用各 ScriptPubKeyManager::Encrypt 加密全部私钥——任何一步失败则 TxnAbort 并直接 assert(false) 终止,让进程重载未加密的钱包,避免"一半密钥已加密、一半未加密"的损坏状态;
  4. 事务提交后调用 GetDatabase().Rewrite() 全量重写钱包文件。源码注释解释了动机:"否则数据库文件的空闲空间(slack space)里可能残留未加密私钥的碎片"——这正是 0.4.0 发布说明中"先备份、后加密、确认后删备份"建议在实现层的呼应;
  5. 最后自动 Unlock 并重建钱包(生成新的 HD 种子/活跃描述符)。

4. 当前仓库中的操作入口

0.4.0 时代的操作入口是 GUI 的 "Options → Encrypt Wallet" 菜单项;当前仓库中的等价操作是 JSON-RPC 接口(GUI 同样通过 RPC 转发,参见 rpcconsole.cppencryptwallet 的命令补全):

$ bitcoin-cli encryptwallet "my pass phrase"
wallet encrypted; The keypool has been flushed and a new HD seed was generated.
You need to make a new backup with the backupwallet RPC.

encrypt.cppencryptwallet 的实现与帮助文本要点:

  • 前置检查:钱包不含私钥(WALLET_FLAG_DISABLE_PRIVATE_KEYS 标志)时报错"nothing to encrypt";已加密钱包再调用则报 RPC_WALLET_WRONG_ENC_STATE(对应 0.4 时代"重复加密"的防护);
  • 参数约束:口令至少 1 个字符,但"应当足够长";
  • 成功后返回提示:keypool 已被清空、生成了新 HD 种子,必须用 backupwallet 重新备份——这正是 0.4.0 文档"加密前先备份 wallet.dat"建议的现代版本化表述;
  • 配套接口:walletpassphrase(临时解锁)、walletlockencrypt.cpp L200-L218 实现,移除内存中的口令)、walletpassphrasechange(更换口令,替代 0.4 时代"重新加密"的需求)。

四、兼容性问题:为什么不能回退到 0.4 之前

0.4.0 发布说明的两条兼容性警告,在当前仓库中都能找到对应实现证据:

  1. 旧版本读不了加密钱包:加密后钱包数据库中的私钥条目由明文私钥变为密文格式,旧版客户端的解析代码没有对应分支,加载即失败(文档表述为"启动时崩溃")。
  2. BDB 4.7 → 4.8 单向性:0.4 起使用 bdb 4.8,其日志文件 4.7 无法读取,因此升级后无法回退。需要说明的是,当前仓库的默认钱包数据库已不再是 Berkeley DB(见 src/wallet/db.cppsrc/wallet/sqlite.cpp 双后端,新钱包默认 SQLite),但"加密后的钱包不可回退到不支持加密的老版本"这一约束本身至今不变。

五、测试验证:wallet_encryption.py

对加密功能的行为验证集中在 test/functional/wallet_encryption.py。该功能测试覆盖的核心断言与发布说明的安全语义一一对应:

  • 加密前可以正常用钱包,encryptwallet 后钱包自动处于锁定状态,发送/签名类操作必须先 walletpassphrase 解锁;
  • 错误口令被拒绝、walletlock 后再次锁死、重复 encryptwallet 报错等路径均有覆盖;
  • 节点重启后加密状态持久化——即 0.4.0 说明中"加密后永远无法使用 0.4 之前的客户端"在现代测试体系中的等价断言。

此外,src/bench/wallet_encrypt.cpp 提供了钱包加密操作的基准测试,可用于观察密钥批量加密在不同硬件上的耗时(这与 DEFAULT_DERIVE_ITERATIONS 的历史注释形成对照)。

六、实践清单(由 0.4.0 发布说明整理)

综合原始发布说明与当前源码实现,执行钱包加密的标准流程为:

  1. 备份:当前版本用 backupwallet RPC 备份钱包文件(0.4 时代是手工拷贝 wallet.dat,路径为 Linux ~/.bitcoin/、macOS /Users/(用户名)/Application Support/Bitcoin/、Windows %APPDATA%/Bitcoin/);
  2. 加密bitcoin-cli encryptwallet "足够长的口令"
  3. 重新备份:加密会生成新 HD 种子与新活跃描述符,旧备份中的私钥结构已失效,必须立即再次 backupwallet
  4. 日常使用:需要发送或签名时 walletpassphrase,用完 walletlock
  5. 口令保管:写在纸上存放在安全处,不与任何其他口令复用,只在客户端内输入;
  6. 心理预期:加密不隐藏地址与交易历史;口令遗忘等于资产丢失,不可恢复。

结语

0.4.0 发布说明篇幅不长,却定下了 Bitcoin Core 钱包加密此后十余年不变的骨架:口令经 SHA-512 多轮迭代派生 AES-256-CBC 密钥加密主密钥,主密钥再加密私钥,IV 用公钥哈希,加密过程事务化并以全量重写杜绝明文残留。今天仓库中 crypter.hwallet.cppencrypt.cpp 里的每一处常量、每一个断言,几乎都能在 2010 年的这份发布说明中找到出处;而 wallet_encryption.py 则保证了这套语义在每个版本上持续可验证。

登录后查看全文
热门项目推荐
相关项目推荐