1. Keystore前世今生:Android安全体系概览、Keystore诞生背景、从软件实现到硬件隔离的演进之路
1.1 为什么我们需要一个专门的密钥存储系统?
做Android安全这些年,我经常被问到同一个问题:「我把密钥存在SharedPreferences里,加密一下不就行了?」
嗯,这个问题其实挺有意思的。你想想看,如果你把家里的钥匙藏在门口的花盆底下,然后给花盆加了一把锁——那这把锁的钥匙你又藏哪儿?
这就是密钥管理的核心困境:密钥需要被保护,但保护密钥的机制本身也需要密钥。说白了,这是一个鸡生蛋蛋生鸡的问题。
在Android早期版本中,开发者确实只能把密钥存在应用私有目录或者SharedPreferences里。我自己早期做的一个支付SDK,就是把RSA私钥用硬编码的AES密钥加密后存起来的。现在回想起来,那简直是「把密码写在便利贴上贴在电脑屏幕边」的行为。
- 软件层面的密钥存储,本质上只是「混淆」而非「保护」
- 攻击者只要拿到root权限,就能读取任何进程的内存和文件
- 密钥的生成、使用、销毁全链路缺乏硬件级隔离
1.2 Android安全体系的三层防线
在讲Keystore之前,我觉得有必要先聊聊Android的整体安全架构。毕竟,Keystore不是孤立的,它是整个安全体系中的一环。
Android的安全体系,我习惯把它分成三层:
| 层级 | 名称 | 核心职责 | 典型技术 |
|---|---|---|---|
| 第一层 | 系统安全层 | 进程隔离、权限控制 | SELinux、沙箱机制、UID隔离 |
| 第二层 | 数据安全层 | 存储加密、通信加密 | File-Based Encryption、TLS/SSL |
| 第三层 | 密钥安全层 | 密钥全生命周期管理 | Android Keystore、TEE、StrongBox |
前两层解决的是「别人能不能拿到你的数据」的问题,而第三层解决的是「即使别人拿到了数据,能不能解密」的问题。Keystore就属于这最关键的一层。
我记得有一次做安全审计,发现某个知名App把支付密钥存在了SQLite数据库里,虽然加密了,但加密密钥就在同一个数据库的另一个表里。这就像把保险柜的密码写在保险柜门上——你说它没加密吧,它确实加密了;你说它安全吧,嗯……
1.3 Keystore的诞生:从API Level 1说起
Android Keystore最早出现在API Level 1(Android 1.0)吗?不,其实不是。
实际上,java.security.KeyStore是Java标准库的一部分,从Android诞生之初就存在了。但那时候它只是一个纯软件的密钥容器,底层依赖Bouncy Castle加密库。说白了,它就是一个「带密码保护的密钥文件」。
真正的转折点出现在Android 4.0(API Level 14),Google引入了AndroidKeyStore provider。这是第一次,Android有了自己的密钥存储服务。
Android 4.0引入的KeyStore provider,虽然名字叫「硬件-backed」,但早期实现其实还是软件模拟的。真正的硬件隔离是从Android 6.0开始才逐步落地的。
1.4 演进之路:从软件到硬件的三次跨越
这条演进之路,我把它总结为三个阶段。每个阶段都对应着当时的安全威胁和硬件能力。
第一阶段:纯软件时代(Android 1.0 - 4.3)
这个阶段,密钥就是一堆字节,存在文件系统里。保护手段无非是:
- 用设备锁屏密码派生一个加密密钥
- 把密钥文件放在应用私有目录
- 依赖Linux文件权限控制访问
说实话,这个阶段的「保护」约等于没有。我做过测试,在root过的设备上,只需要一条adb shell命令就能把任何应用的密钥文件拷出来。
我曾经在一个项目中,发现同事用KeyStore存储了OAuth的refresh token。但他不知道的是,在Android 4.3及以下版本,KeyStore的master key是写死在系统源码里的。这意味着所有设备的master key都一样——嗯,你懂的。
第二阶段:硬件辅助时代(Android 6.0 - 8.0)
Android 6.0引入了两个关键特性:
- 硬件-backed KeyStore:密钥操作委托给TEE(Trusted Execution Environment)
- KeyGenParameterSpec:开发者可以指定密钥的用途、有效期、用户认证要求
这个阶段,密钥本身可能还在软件层,但密钥的使用操作(签名、解密)是在TEE中完成的。这意味着即使系统被攻破,攻击者也拿不到密钥的明文,只能调用接口让它干活。
我举个例子你就明白了:
// Android 6.0+ 的密钥生成方式
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_rsa_key",
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true) // 需要用户最近解锁过设备
.setKeySize(2048)
.build();
KeyPairGenerator generator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
注意看setUserAuthenticationRequired(true)这一行。这意味着每次使用这个密钥,都需要用户最近解锁过设备。如果设备被偷了,小偷即使拿到了系统权限,也没法用这个密钥——因为他不知道锁屏密码。
第三阶段:硬件隔离时代(Android 9.0+)
Android 9.0引入了StrongBox KeyStore。这玩意儿是什么?
说白了,就是在手机主板上单独放一颗安全芯片,专门干密钥相关的活。这颗芯片有自己的CPU、内存、存储,甚至有自己的真随机数发生器。它和主系统之间只有一条有限的通信通道。
StrongBox的特点:
- 物理隔离:密钥永远不会离开安全芯片
- 防侧信道攻击:芯片设计时就考虑了功耗分析、电磁辐射等攻击
- 有限速率:签名操作有速率限制,防止暴力破解
- 独立认证:芯片需要经过Common Criteria EAL4+认证
从「把钥匙藏在花盆里」→「把钥匙存在保险柜里」→「把钥匙存在一个只有特定人才能打开的保险柜里,而且这个保险柜还会自己报警」。
1.5 一张图看懂Keystore架构演进
下面这张图,是我自己整理的Keystore架构演进脉络。你可以看到,每一层都是在前一层基础上增加了硬件隔离的深度。
1.6 为什么硬件隔离这么重要?
你可能觉得,软件实现也能加密啊,为什么非要硬件?
我讲一个真实案例吧。2018年,我参与了一个金融App的安全加固项目。客户要求密钥必须存储在硬件隔离环境中。当时他们用的是某厂商的手机,支持TEE。
我们做了个测试:用熔断攻击(Fault Injection)尝试绕过TEE的保护。结果发现,虽然TEE确实能防住软件层面的攻击,但通过电压毛刺(Voltage Glitch)还是有可能让TEE返回错误的结果。
这就是为什么Google后来推出了StrongBox——它不仅有TEE的软件隔离,还有物理层面的防护。安全芯片会监测电压、频率、温度,一旦发现异常就自动销毁密钥。
如果你的App处理的是金融、医疗、身份认证等敏感数据,尽量使用StrongBox。虽然签名速度会慢一些(大约100ms vs TEE的10ms),但安全性提升了一个数量级。
1.7 小结:Keystore的演进本质
回顾Keystore的演进,你会发现一个清晰的脉络:
- 从「不信任任何人」到「信任硬件」:早期我们信任操作系统,后来信任TEE,现在信任独立的安全芯片
- 从「密钥可导出」到「密钥不可见」:密钥从文件里的字节,变成了芯片内部永远无法读取的「影子」
- 从「单点防护」到「纵深防御」:不仅有硬件隔离,还有用户认证、速率限制、防回滚等多层保护
嗯,这一章的内容就到这里。Keystore的演进之路,说白了就是一场「攻击者越来越强,防御者不得不把密钥藏得越来越深」的军备竞赛。而作为开发者,我们要做的就是在安全性和易用性之间找到那个平衡点。
公众号:蓝海资料掘金营,微信deep3321