上链ON-CHAIN BLOCK 23 / 26
卷五 · Android 原生BLOCK 23

移动端密钥安全


如果你选择自管私钥(做钱包 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 私钥被它锁在保险箱里。

Kotlin · 用 Keystore 加密以太坊私钥(信封加密)
// 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 里安全地读链、签名、连钱包了。最后一卷,我们潜入协议深水区。

本章小结

◆ 返回目录