18. Keystore密钥备份:备份限制策略、密钥材料导出限制、云备份安全机制
聊到密钥备份,很多开发者的第一反应是:「直接把密钥文件拷出来不就行了?」
嗯,如果你真这么干过,大概率会发现——根本导不出来。这不是系统在跟你作对,而是Android Keystore从设计上就堵死了这条路。
我当年第一次做密钥迁移时,也天真地以为能拿到密钥的原始字节。结果折腾了两天,最后老老实实去查文档。说白了,Keystore的备份机制,核心就一句话:密钥可以迁移,但密钥材料不能导出。
核心原则:Android Keystore 保证密钥在硬件安全模块(TEE/StrongBox)内部生成和使用,密钥明文永远不会离开安全环境。
18.1 备份限制策略:谁允许备份?
不是所有密钥都能备份。系统会根据密钥的创建参数,决定它能不能跟着用户的Google账号走。
| 密钥属性 | 是否允许备份 | 说明 |
|---|---|---|
| KeyProperties.PURPOSE_ENCRYPT | ✅ 允许 | 加密密钥可以备份,但备份的是密钥引用,不是明文 |
| KeyProperties.PURPOSE_SIGN | ✅ 允许 | 签名密钥同样可以备份引用 |
| KeyProperties.PURPOSE_VERIFY | ✅ 允许 | 验证密钥通常可以公开,备份无风险 |
设置了 setUnlockedDeviceRequired(true) |
❌ 禁止 | 这类密钥要求设备解锁状态,备份后无法保证 |
设置了 setUserAuthenticationRequired(true) |
❌ 禁止 | 需要生物认证的密钥,备份到新设备后认证信息丢失 |
我遇到过这样一个坑:有个金融App的密钥创建时加了 setUserAuthenticationRequired(true),结果用户换手机后,密钥备份过去了但指纹认证一直失败。为什么?因为新设备的生物特征模板跟旧设备完全不同,密钥绑定的认证令牌自然就失效了。
注意:即使密钥允许备份,备份的也只是密钥的「元数据」和「加密后的包装体」。真正的密钥材料仍然留在原设备的TEE里。
18.2 密钥材料导出限制:为什么你拿不到原始密钥?
这是Keystore最核心的安全设计。我经常跟团队说:Keystore不是文件系统,它是个黑盒。
你调用 KeyStore.getEntry() 拿到的,其实是 KeyStore.SecretKeyEntry 或 KeyStore.PrivateKeyEntry。但这些对象内部持有的密钥引用,指向的是TEE里的安全内存地址。你没法通过 getEncoded() 拿到原始字节——因为实现类直接返回 null。
// 你拿不到这个
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey("my_key", null);
byte[] raw = key.getEncoded(); // 返回 null!
为什么会这样?
说白了,这是硬件强制隔离。TEE里的密钥材料,对REE(普通执行环境)是不可见的。就算你root了手机,也读不到TEE内存。我见过有人尝试用 ptrace 去dump进程内存,结果发现密钥根本不在应用进程空间里——它只在TEE里存在。
我的建议:如果你确实需要跨设备共享密钥,不要试图绕过Keystore。正确的做法是使用「密钥协商」或「包装密钥导出」API(Android 9+ 支持 WrappedKeyEntry)。
18.3 云备份安全机制:密钥是怎么上云的?
Android的云备份(Google Drive备份)对Keystore密钥做了特殊处理。流程大致如下:
- 备份触发:系统遍历所有应用的Keystore条目,筛选出允许备份的密钥。
- 密钥包装:每个密钥被一个「传输密钥」加密包装。这个传输密钥由Google的备份服务生成,存储在云端HSM里。
- 加密上传:包装后的密钥数据上传到Google Drive,但传输密钥本身不会跟备份数据一起存储。
- 恢复流程:用户在新设备上恢复备份时,系统从云端HSM获取传输密钥,解密包装体,再重新注入到新设备的TEE中。
这里有个关键点:传输密钥的访问受用户Google账号密码保护。也就是说,就算有人拿到了你的备份文件,没有你的账号密码,也解不开密钥包装。
我记得有一次排查线上问题,发现某个用户的密钥恢复后签名一直失败。查了半天,原来是用户在新设备上恢复了备份,但新设备的TEE固件版本跟旧设备不一致,导致密钥重新注入时兼容性出了问题。嗯,这种问题在跨厂商迁移时尤其常见。
安全层级总结:
- 第一层:Google账号密码保护传输密钥
- 第二层:传输密钥存储在云端HSM,不落盘
- 第三层:密钥材料在TEE内加密包装,REE不可见
- 第四层:恢复后密钥重新注入TEE,明文永不暴露
18.4 知识体系总览
下面这张图把整个备份机制串起来了。你仔细看,会发现所有路径最终都指向同一个结论:密钥材料不出TEE。
18.5 避坑指南
最后分享几个我踩过的坑,希望能帮你少走弯路:
- 不要依赖密钥备份做跨设备同步:备份机制是为「换机恢复」设计的,不是为「多设备实时同步」设计的。如果你需要多设备共享密钥,请使用端到端加密的密钥分发协议。
- 测试时注意设备差异:不同厂商的TEE实现有差异。我在某款国产手机上遇到过密钥恢复后签名结果不一致的问题,最后发现是TEE的RNG实现不同导致的。
- 用户注销账号时清理密钥:如果用户退出登录,记得调用
KeyStore.deleteEntry()删除本地密钥引用。虽然密钥材料在TEE里,但引用删除后应用就无法再使用了。 - 不要尝试绕过导出限制:我见过有人试图通过
KeyStore.getCertificate()获取公钥后反推私钥——这不可能。Keystore的私钥永远不会暴露给应用层。
一句话总结:Keystore的备份机制,本质上是「密钥引用」的备份,而不是「密钥材料」的备份。理解了这个区别,你就不会再犯「导出密钥」这种低级错误了。
公众号:蓝海资料掘金营,微信deep3321