移动端密钥安全
如果你选择自管私钥(做钱包 App),这一章就是整本书里责任最重的一章。手机是个丢失率极高、随时联网、装满了不可信 App 的环境,而你要在上面保管等同于现金的私钥。这里没有"差不多就行"——一个错误就是用户资产清零。好在,你的 Android 安全经验(尤其 Keystore)在这里价值连城。
核心矛盾:私钥必须存,又绝不能泄
钱包 App 需要私钥来签名(第 3、21 章),所以它得存在设备上某处。但只要它以明文形式出现在可被读取的地方——SharedPreferences、普通文件、日志、内存 dump——就可能被恶意 App、被 root 后的系统、被取证工具拿走。目标是:让私钥永远不以明文离开受硬件保护的安全区。
Android Keystore:你已有的利器
这正是 Android Keystore 的用武之地(如果你读过本站《密语》密码学小书的 Android Keystore 章,这里是它在 Web3 场景的落地)。它的杀手锏是:密钥可以生成并驻留在硬件安全模块(TEE 或 StrongBox 安全芯片)里,App 只能"请求用它做运算",永远拿不到密钥本身的明文。
坏消息:Android Keystore 目前原生不支持以太坊用的 secp256k1 曲线(它支持 NIST P-256 等)。所以你不能直接让 Keystore 保管以太坊私钥并用它签交易。现实中的正确姿势是信封加密(envelope encryption):用 Keystore 里那把永不导出的硬件密钥,去加密你的以太坊私钥/助记词,把密文存到磁盘;签名时先用 Keystore 解密拿到明文私钥(仅在内存中短暂存在)、签完立即清除。硬件密钥守大门,secp256k1 私钥被它锁在保险箱里。
// 1) 在硬件里生成一把 AES 密钥,要求用户认证 + 尽量用 StrongBox val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") keyGen.init( KeyGenParameterSpec.Builder("wallet_wrapping_key", KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) // 签名前需指纹/PIN .setIsStrongBoxBacked(true) // 有安全芯片就用(需处理不支持的回退) .build() ) val wrappingKey = keyGen.generateKey() // 2) 用它加密以太坊私钥,只把密文 + iv 落盘(明文私钥用完即擦) val cipher = Cipher.getInstance("AES/GCM/NoPadding") cipher.init(Cipher.ENCRYPT_MODE, wrappingKey) val ciphertext = cipher.doFinal(ethPrivateKeyBytes) // 存 ciphertext + cipher.iv;明文 ethPrivateKeyBytes 立刻用 0 覆盖
setUserAuthenticationRequired(true) 把签名和生物识别绑定——用户每次发交易都要指纹/面容/PIN,和你给敏感操作加 BiometricPrompt 是一回事。StrongBox 则是那颗独立安全芯片(类似部分设备的独立安全单元)。这些你在做支付、密钥保护类需求时都碰过,只是这次保护的对象是"等于用户全部资产的那把钥匙",容错率为零。
助记词:人类可读的备份(第 3 章回归)
回顾第 3 章:BIP-39 把私钥编码成 12/24 个单词,BIP-32/44 从一套助记词分层派生出整棵账户树(路径 m/44'/60'/0'/0/0,60' 是以太坊)。钱包 App 通常这样组织:
- 首次创建:生成助记词 → 强制用户离线抄写备份(这是唯一的恢复途径) → 由助记词派生私钥 → 私钥用上面的信封加密存起来。
- 日常使用:从加密存储解密出私钥,签名,清除。
- 换设备恢复:用户输入助记词,重新派生出同一套账户。
展示/输入助记词的界面是攻击焦点:禁止截图(FLAG_SECURE)、不进剪贴板、不写日志、不做云备份、键盘用不联网的安全输入。还要防"钓鱼恢复"——绝不在用户没主动发起恢复时索要助记词。你保管的是用户的全部身家,任何一处疏忽都可能上新闻。这也是为什么很多团队宁可用 WalletConnect(上一章)把这份责任推给专业钱包。
更前沿的方向:不再有"单一私钥"
"一把私钥统治一切、丢了就全没、泄了就全丢"是糟糕的用户体验。业界正在用两条路线解决它:
| 方案 | 思路 | 好处 |
|---|---|---|
| MPC 钱包 | 私钥被切成多份分布式保管,签名时多方协作计算,完整私钥从不组装出来 | 无单点泄露;可社交恢复;无需助记词 |
| 账户抽象 (AA) | 账户是智能合约,签名/恢复规则可编程(第 26 章) | 可用 passkey、多签、社交恢复、代付 gas |
| Passkey 钱包 | 用手机安全芯片里的 passkey(P-256)做签名,配合 AA 合约 | 无助记词,生物识别即用,体验接近 Web2 |
这些方向让"钱包"越来越不像"一串要用命守护的助记词",越来越像"一个有恢复机制、多重保险的账户"。第 26 章会专门讲账户抽象——它很可能是移动端 Web3 大规模落地的关键。
移动端保管私钥的黄金法则:私钥绝不明文落盘。用 Android Keystore 的硬件密钥做信封加密(因为 Keystore 不支持 secp256k1),签名时才短暂解密、用完即擦,并用生物识别 + StrongBox 加固。助记词是唯一恢复途径,其展示/输入界面要按最高安全标准防护。若不想背这份责任,就用 WalletConnect;若想要更好体验,关注 MPC 与账户抽象。至此卷五完成——你已能在原生 App 里安全地读链、签名、连钱包了。最后一卷,我们潜入协议深水区。
本章小结
- 私钥必须存于设备又绝不能明文泄露;目标是让它永不以明文离开硬件安全区。
- Android Keystore 不支持 secp256k1,故用它的硬件密钥做信封加密来保护以太坊私钥,签名时短暂解密即擦。
- 用 setUserAuthenticationRequired + StrongBox 加固;助记词是唯一恢复途径,界面按最高标准防护(FLAG_SECURE、禁剪贴板/日志/云备份)。
- 前沿方向 MPC、账户抽象、passkey 钱包正在消解"单一私钥"的脆弱性。