7、密钥管理与 Android KeyStore:KeyStore 介绍、生成对称密钥、生成非对称密钥、密钥有效期设置、密钥访问控制
说到生物识别,很多人第一反应是「指纹一按,门就开了」。但作为开发者,我们得想得更深——指纹验证通过后,真正用来加密数据的密钥,到底存在哪?怎么保证它不被偷走?
嗯,这就是 Android KeyStore 要解决的问题。它不是一个普通的文件存储,而是一个硬件隔离的保险箱。我个人习惯把 KeyStore 比作「钥匙管家」——它帮你保管密钥,但从不把密钥本身交出去。你只能通过它来使用密钥,比如加密、解密、签名。
KeyStore 是什么?
Android KeyStore 是系统提供的一个密钥管理容器。它有几个关键特性:
- 硬件隔离:密钥存储在 Trusted Execution Environment(TEE)或 Secure Element(SE)中,App 进程无法直接读取
- 导出禁止:一旦密钥生成,它的明文永远不会离开安全硬件
- 使用绑定:可以限制密钥只能在用户解锁设备后使用,或者只能在生物识别验证通过后使用
核心要点:KeyStore 不存储数据,只存储密钥。你的加密数据可以放 SharedPreferences、Room 或者文件里,但密钥必须交给 KeyStore 管理。
生成对称密钥
对称密钥,说白了就是加密和解密用同一把钥匙。在 Android 上,我们通常用 AES 算法。我建议使用 AES-GCM 模式,因为它自带认证标签,能防止数据被篡改。
下面是我项目中常用的生成方式:
// 生成 AES 对称密钥
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_aes_key", // 密钥别名,后续通过它引用
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build();
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(spec);
keyGenerator.generateKey();
个人经验:我曾经在项目里用了 AES-CBC 模式,结果发现每次加密结果都一样,因为忘了加 IV。后来换成 GCM 模式,它自动处理了随机 IV,省心很多。
生成非对称密钥
非对称密钥,就是一对钥匙——公钥公开,私钥保密。在 KeyStore 里,私钥同样无法导出。我通常用 RSA 算法来做密钥对生成。
为什么需要非对称密钥?举个例子:你的 App 需要和服务器安全通信,服务器可以用你的公钥加密数据,只有你的私钥能解密。私钥在 KeyStore 里,谁也拿不走。
// 生成 RSA 非对称密钥对
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_rsa_key_pair",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setDigests(KeyProperties.DIGEST_SHA256)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
.setKeySize(2048)
.build();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(spec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
注意:RSA 密钥长度不要低于 2048 位。1024 位的 RSA 在 2024 年的安全标准下已经不够用了。我见过有老项目还在用 1024,后来被安全审计要求全部升级。
密钥有效期设置
密钥不能永生。你想想看,如果一把钥匙永远不换,被破解的风险会越来越高。KeyStore 支持设置密钥的有效期,过期后密钥就无法使用了。
设置方式很简单,在 Builder 里加上时间限制:
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_timed_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeyValidityStart(Calendar.getInstance().getTime())
.setKeyValidityEnd(getDateAfterDays(365)) // 一年后过期
.build();
我个人习惯把有效期设成 90 天。为什么?因为如果密钥泄露,90 天的窗口期比一年小得多。而且定期轮换密钥,本身就是一种好的安全实践。
避坑指南:我曾经把有效期设成了 0 天,结果密钥生成后立刻过期,加密数据后无法解密。调试了半天才发现是日期计算出了问题。所以记得用 Calendar 或 Instant 来算时间,别手写毫秒数。
密钥访问控制
这是 KeyStore 最强大的地方。你可以控制密钥在什么条件下才能被使用。常见的控制方式有:
- 用户认证绑定:密钥只能在用户通过生物识别或锁屏密码后使用
- 设备锁屏绑定:设备锁屏后,密钥自动失效
- 时间窗口绑定:只能在指定时间段内使用
最常用的是用户认证绑定。比如你的支付 App,只有在指纹验证通过后,才能用私钥签名交易数据。
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
"my_biometric_key",
KeyProperties.PURPOSE_SIGN)
.setUserAuthenticationRequired(true) // 需要用户认证
.setUserAuthenticationValidityDurationSeconds(30) // 认证后30秒内有效
.build();
小技巧:setUserAuthenticationValidityDurationSeconds 设成 0 表示每次使用都需要重新认证。设成正数表示认证后一段时间内可以重复使用。我一般设成 5 秒,既不用频繁按指纹,又不会让密钥长期暴露。
知识体系结构图
下面这张图帮你理清 KeyStore 的核心逻辑:
总结一下
KeyStore 的核心价值就一句话:把密钥锁进保险箱,你只能隔着玻璃用,不能伸手拿。对称密钥适合大量数据加密,非对称密钥适合密钥交换和签名。有效期和访问控制是安全的两道锁,别偷懒不设。
嗯,我记得刚开始接触 KeyStore 时,总觉得它麻烦——明明一个字符串就能当密钥,干嘛要绕这么大一圈?后来项目上线,看到安全审计报告里「密钥存储合规」那一项打了勾,才明白这步路没白走。