Android 的 Keystore 组件承担着应用身份认证、加密数据保护等关键职责。与直接把密钥存放在 SharedPreferences 或普通文件相比,Keystore 允许开发者将密钥材料托管给系统服务,由系统在独立进程和硬件支持的保护下完成加解密与签名操作。这样即使应用沙箱被突破,攻击者也很难导出原始密钥。理解这套机制,需要从密钥的生成、存储位置以及授权策略几个层面入手。

在多数现代 Android 设备上,Keystore 并非把密钥明文写入磁盘,而是将私钥或对称密钥封装在系统服务可访问的加密容器中。系统服务本身也不直接持有密钥明文,而是通过 HAL 层与硬件安全模块交互。部分设备还支持 StrongBox,即独立的安全芯片,密钥永远不离开该芯片,任何加解密和签名操作都在芯片内部完成。这为密钥安全提供了物理级的隔离边界。
Android Keystore 的密钥保护与隔离机制
Android Keystore 的核心设计目标是在应用进程不接触原始密钥的前提下完成密码学运算。从 Android 6.0 开始,系统引入了密钥库的硬件抽象层,允许厂商将密钥材料存储在 TEE(可信执行环境)或 SE(安全元件)中。当应用调用 KeyStore.getKey 获取密钥时,返回的并非真正的密钥字节,而是一个引用对象,真实的密钥操作通过 IPC 转发给系统服务,再由系统的密钥守护进程调度到安全硬件。
这种设计带来两个直接好处:其一,即使应用被反编译或内存被读取,攻击者看到的也只是一堆引用,无法还原出可用的密钥;其二,密钥的使用策略由系统强制执行,诸如只有在用户最近完成指纹或 PIN 认证后、或者只允许用于特定算法和模式,这些限制无法被应用自身绕过。例如,在生成密钥时指定 setUserAuthenticationRequired(true),系统会要求每次使用密钥前进行生物识别或设备凭据验证,而应用无法删除这一约束。
此外,Keystore 还通过密钥版本绑定和防回滚机制防止旧密钥被恢复使用。设备在系统升级或安全补丁更新后,可以限制某些过时算法的密钥继续参与解密,减少因算法弱点被利用的风险。对比直接使用 Java 密码学扩展生成密钥并存储在应用私有目录,Keystore 在保护密钥不被提取方面具有明显优势。
生成与使用 KeyStore 密钥的实践代码
要在应用中使用 Android Keystore,首先需要在 AndroidManifest 中声明必要的权限并不需要常规的存储权限,因为 Keystore 的访问由系统管理。实际开发中,生成一把 AES 对称密钥的典型方式如下,代码通过 KeyGenParameterSpec.Builder 明确限定密钥用途、加密模式和填充方式,从而避免密钥被滥用于其他算法。
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
new KeyGenParameterSpec.Builder("my_symmetric_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(30)
.build()
);
SecretKey secretKey = keyGenerator.generateKey();
生成密钥后,应用可以从 Keystore 中获取该密钥的引用,并用于实际加解密。注意,当用户认证有有效期设置时,每次加密或解密操作都会触发系统检查认证状态,如果超出有效期,系统会抛出 UserNotAuthenticatedException。下面是一个使用该密钥进行 AES-GCM 加密的示例。
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Key key = keyStore.getKey("my_symmetric_key", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(plaintext);
对于需要存储非对称密钥的场景,例如用于 HTTPS 双向认证或数字签名,可以使用 KeyPairGenerator 并指定 KeyProperties.KEY_ALGORITHM_RSA 或 EC。此时必须设置签名或验证用途,并可以要求密钥由 StrongBox 支持,通过 setIsStrongBoxBacked(true) 声明硬件偏好。如果设备不支持 StrongBox,该参数会被忽略,密钥仍然会落入 TEE 保护,保持基本的安全等级。
常见安全误区与加固建议
开发者最容易犯的错误之一是把 Keystore 当作万能保险箱,忽略了密钥使用授权策略的重要性。如果没有设置 setUserAuthenticationRequired 或没有限制密钥用途,那么一旦应用进程被恶意代码注入,攻击者同样可以利用这把密钥完成解密或签名。正确的做法是:为高价值数据强制绑定用户认证,并设置较短的认证有效期;对密钥用途和加密模式进行严格控制,避免一把密钥同时用于多种协议。
另一个常见误区是在 root 后的设备上过度信任 Keystore。虽然 TEE 和 StrongBox 能够抵御普通的应用层攻击,但如果攻击者获得了 root 权限或解锁了 bootloader,系统完整性检测可能被绕过。此时,攻击者虽然仍无法直接导出 StrongBox 中的密钥,但可以通过 hook 系统调用或替换 framework 层来请求合法密钥操作。针对这种情况,建议应用结合 SafetyNet 或 Play Integrity API 检测设备完整性,并在检测到 root 或解锁状态时拒绝执行敏感操作。
还需要注意,Android Keystore 与 Java 标准 KeyStore 在持久化方面存在差异:存储在 Android Keystore 中的密钥是系统级的,应用卸载时会被自动删除,但备份恢复可能导致密钥丢失或出现无法匹配的密文。因此,如果应用依赖密钥解密本地数据,需要提前设计密钥恢复或云端备份方案,避免用户换机后数据无法解密。对于签名用途的密钥,建议定期轮换并使用密钥别名版本化,防止长期使用同一密钥带来被逆向分析的风险。
最后,不要忽视 API 等级的兼容性。KeyGenParameterSpec 从 Android 6.0 才完整可用,旧版本需要使用 KeyPairGeneratorSpec,而 StrongBox 则需要 Android 9.0 及以上版本。开发者应根据项目最低版本进行条件分支,并在不支持硬件密钥的设备上提供降级方案,例如使用系统级加密的 SQLCipher 额外保护,而不是直接退回明文存储。
Android Keystore密钥存储硬件安全模块修改时间:2026-10-03 08:31:09