2. Android Keystore 系统架构与安全等级:TEE 与 StrongBox、KeyGenParameterSpec、密钥用途与访问控制
Android Keystore 是 Android 系统提供的硬件级密钥管理基础设施,它将密钥材料与应用进程隔离,并通过可信执行环境(TEE)或独立安全芯片(StrongBox)实现加密运算。对于蓝牙敏感数据的持久化加密而言,理解 Keystore 的层级架构、安全等级差异以及 KeyGenParameterSpec 的精细控制是正确实现安全存储的前提。本章将围绕系统架构、安全等级、密钥参数规范与访问控制四个核心维度展开,并结合蓝牙密钥持久化的实际场景进行讲解。
Android Keystore 系统架构总览
Android Keystore 采用分层架构设计,从上到下依次为应用层、框架层、系统服务层和硬件安全层。密钥材料一旦在硬件安全层中生成,其明文永远不会进入应用进程内存,所有加密操作均在安全环境中完成。下图展示了这一完整链路:
安全等级:TEE 与 StrongBox 对比
Android Keystore 根据设备硬件能力提供两种安全后端,其安全等级、性能和适用场景有明显差异。蓝牙加密场景中应根据数据的敏感程度和操作频率选择合适的安全等级。
| 对比维度 | TEE (Trusted Execution Environment) | StrongBox (独立安全芯片) |
|---|---|---|
| 隔离方式 | 硬件隔离(与主 OS 共享 CPU,但内存和寄存器隔离) | 物理隔离(独立芯片、独立 CPU、独立存储) |
| 安全认证 | 通常无独立认证等级 | CC EAL 4+ 或更高 |
| 防物理攻击 | 需要依赖 SoC 防护机制 | 具备防篡改、防侧信道攻击能力 |
| 运算速度 | 较快(与主 SoC 共享部分资源) | 较慢(独立芯片,运算能力有限) |
| 支持的算法 | 完整:AES/RSA/EC/HMAC 等 | 有限:仅 AES-256/EC P256/SHA 等核心算法 |
| 蓝牙数据推荐场景 | 常规加密密钥、临时令牌 | 长期静态密钥、身份凭证、防回滚数据 |
KeyGenParameterSpec 核心参数
KeyGenParameterSpec 是 Android Keystore 中定义密钥生成规则的规范类,通过 Builder 模式配置密钥的算法、用途、访问控制和安全等级。以下是与蓝牙加密持久化最相关的参数:
| Builder 方法 | 参数说明 | 蓝牙场景典型值 |
|---|---|---|
setKeySize(int) |
密钥长度(位) | 256(AES-256) |
setBlockModes(String...) |
分组密码模式 | BLOCK_MODE_GCM |
setEncryptionPaddings(String...) |
填充方案 | ENCRYPTION_PADDING_NONE(GCM 无需填充) |
setKeyPurpose(int) |
密钥用途(可组合) | PURPOSE_ENCRYPT | PURPOSE_DECRYPT |
setUserAuthenticationRequired(boolean) |
是否需用户认证后才能使用密钥 | true(高安全场景) |
setUserAuthenticationValidityDurationSeconds(int) |
用户认证后的有效期(秒) | 60 或 300 |
setIsStrongBoxBacked(boolean) |
是否强制使用 StrongBox | true(持久化 LTK 时建议开启) |
setInvalidatedByBiometricEnrollment(boolean) |
新增生物识别时是否令现有密钥失效 | true(安全继承) |
密钥用途与访问控制策略
密钥用途(Key Purpose)定义了密钥可以执行的操作类型,访问控制则约束了密钥在何种条件下才能被使用。两者共同构成 Keystore 权限模型的核心。
密钥用途常量(KeyProperties):
PURPOSE_ENCRYPT— 加密数据PURPOSE_DECRYPT— 解密数据PURPOSE_SIGN— 生成数字签名PURPOSE_VERIFY— 验证数字签名PURPOSE_WRAP_KEY— 加密导出另一个密钥
访问控制策略(按安全强度从低到高):
- 无认证访问 — 密钥可在应用进程内直接使用,无需用户交互。适用于后台服务中的低敏数据加密。
- 锁屏认证访问 — 要求设备处于解锁状态(
setUserAuthenticationRequired(true)),适合应用在前台时的加密操作。 - 生物识别认证访问 — 每次使用密钥都需要生物识别(指纹/面部),有效期由
setUserAuthenticationValidityDurationSeconds控制。 - 时间窗口认证访问 — 一次认证后在有效期内可多次使用密钥,避免频繁弹窗。
BluetoothGatt 回调)中如果密钥设置了用户认证要求且认证超时,会导致加解密操作抛出 KeyStoreException(需重新认证)。正确做法是将短期加密密钥与长期存储密钥分离,短期密钥不设用户认证。
实现流程:生成 TEE/StrongBox 密钥
以下代码演示如何在 Android Keystore 中生成一个用于蓝牙数据持久化加密的 AES-256/GCM 密钥,并指定使用 StrongBox 安全芯片:
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import java.security.KeyStore;
import javax.crypto.KeyGenerator;
// 1. 构建密钥规范
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"bluetooth_ltk_encrypt_v1",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(120)
.setIsStrongBoxBacked(true)
.setInvalidatedByBiometricEnrollment(true)
.build();
// 2. 初始化 KeyGenerator(使用 AndroidKeyStore Provider)
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
keyGenerator.init(spec);
// 3. 生成密钥(密钥材料直接写入 StrongBox,永不进入应用内存)
keyGenerator.generateKey();
// 4. 验证密钥是否已存储
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
boolean exists = keyStore.containsAlias("bluetooth_ltk_encrypt_v1");
// exists == true 表示密钥已安全持久化
调试与安全等级验证
在开发调试阶段,需要验证密钥是否正确使用了预期的安全后端。以下方法可以有效确认密钥的实际存储位置:
方法一:通过 KeyStore 元数据验证
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Key key = keyStore.getKey("bluetooth_ltk_encrypt_v1", null);
// 检查密钥实现的类名,判断后端
if (key.getClass().getName().contains("StrongBox")) {
// 密钥存储在 StrongBox 中
} else if (key.getClass().getName().contains("TEE")) {
// 密钥存储在 TEE 中
} else {
// 回退到软件安全容器(不推荐)
}
方法二:使用 adb shell dumpsys 查看 Keystore 状态
# 查看所有密钥别名和元数据
adb shell dumpsys keystore
# 筛选特定密钥的详细信息
adb shell dumpsys keystore | grep -A 10 "bluetooth_ltk_encrypt_v1"
方法三:检查 StrongBox 可用性(避免运行时崩溃)
import android.security.keystore.KeyProperties;
// 在生成密钥前检查设备是否支持 StrongBox
boolean strongBoxAvailable = KeyStore.getInstance("AndroidKeyStore")
.getKeyStoreProvider()
.toString()
.contains("StrongBox");
if (!strongBoxAvailable) {
// 降级到 TEE 或提示用户设备不满足安全要求
}
常见错误与性能优化
- NoSuchAlgorithmException: 设备 StrongBox 不支持指定算法(如
ECDSA可能受限)。解决方案:在生成前查询KeyProperties支持的算法列表。 - KeyStoreException:密钥已存在: 重复调用
generateKey()且使用相同别名会抛异常。应在生成前先检查containsAlias(),或先删除旧密钥再生成。 - 用户认证超时: 后台蓝牙服务在认证窗口外使用密钥导致操作失败。建议将认证超时设为
-1(每次需认证)或使用独立的非认证密钥处理后台加密。 - StrongBox 性能骤降: StrongBox 适合低频小数据量操作。蓝牙加密频繁时(如大量属性写入),应改用 TEE 后端的临时密钥。
- 密钥分层: 使用 StrongBox 存储主密钥(KEK),用主密钥在 TEE 中派生会话密钥(DEK),既保证长期密钥安全,又提升高频加密性能。
- 批量加密预加载: 在蓝牙连接建立阶段一次性从 Keystore 加载密钥到会话缓存,避免每次 GATT 写入都访问硬件安全区。
- 异步非阻塞调用: StrongBox 操作耗时可能达到 50–200ms,应在
AsyncTask或协程中执行,避免阻塞蓝牙主线程。 - 密钥轮替策略: 蓝牙长期密钥(LTK)每 6–12 个月轮替一次,轮替时通过
keyStore.deleteEntry()移除旧密钥,再生成新密钥写入 StrongBox。