12. Keystore密钥用途:签名、加密、解密、密钥协商、密钥派生,多用途组合策略
各位好,今天我们来聊聊 Keystore 里密钥的「用途」问题。说白了,就是一把密钥能干什么、不能干什么,以及怎么组合着用。
我刚开始接触 Android Keystore 时,觉得这东西就是个保险箱——把密钥扔进去就完事了。后来踩了不少坑才明白,密钥的用途(KeyPurpose)才是真正决定安全策略的核心。你想想看,如果一把万能钥匙既能开门又能当螺丝刀用,那安全性从何谈起?
12.1 密钥用途的本质
Keystore 里的每个密钥都有一个「用途标签」。这个标签不是摆设,它直接决定了密钥能参与哪些密码学操作。说白了,就是给密钥上了一道紧箍咒。
Android 支持以下几种基本用途:
- 签名(SIGN):用于生成数字签名,比如 APK 签名、数据完整性校验
- 验证(VERIFY):验证签名是否有效,通常与 SIGN 成对出现
- 加密(ENCRYPT):用公钥或对称密钥加密数据
- 解密(DECRYPT):解密被加密的数据
- 密钥协商(KEY_AGREEMENT):比如 ECDH,双方交换密钥材料
- 密钥派生(KEY_DERIVATION):从一个主密钥派生出子密钥
核心原则:每个密钥只做它该做的事。签名密钥别拿去加密,加密密钥别拿去签名。这是安全设计的基本功。
12.2 单用途 vs 多用途
我在项目中遇到过不少团队,图省事把密钥用途设成「万能」的。嗯,这里要注意——虽然技术上允许,但安全上是大忌。
举个例子:
// 不推荐:一把密钥什么都能干
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
"my_key",
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY |
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
);
// 推荐:分开用途,各司其职
KeyGenParameterSpec signKeySpec = new KeyGenParameterSpec.Builder(
"sign_key",
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY
).build();
KeyGenParameterSpec encryptKeySpec = new KeyGenParameterSpec.Builder(
"encrypt_key",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
).build();
为什么要分开?我跟你讲个真实案例。曾经有个金融类 App,把用户身份签名密钥和数据加密密钥混用。结果签名算法有个侧信道漏洞,攻击者通过分析签名时间,硬是把加密密钥给还原了。你说冤不冤?
12.3 密钥协商与派生
这两个用途比较特殊,我单独拿出来说。
密钥协商(KEY_AGREEMENT):主要用于 ECDH 这类协议。双方各自生成密钥对,通过交换公钥来计算出共享密钥。我在做即时通讯 SDK 时,就用这个来建立端到端加密通道。
密钥派生(KEY_DERIVATION):从一个根密钥派生出多个子密钥。比如你有一个主密钥,可以派生出加密密钥、认证密钥、会话密钥等。Android 9 以上支持 HKDF 派生。
我的习惯:密钥协商和派生通常配合使用。先协商出一个共享密钥,再用它派生出具体用途的子密钥。这样即使某个子密钥泄露,也不会影响主密钥的安全。
12.4 多用途组合策略
实际项目中,很少只用单一用途。我总结了一套组合策略,供你参考:
| 场景 | 推荐用途组合 | 说明 |
|---|---|---|
| 用户身份认证 | SIGN + VERIFY | 签名用于证明身份,验证用于检查 |
| 数据加密存储 | ENCRYPT + DECRYPT | 对称密钥或非对称密钥对 |
| 端到端加密 | KEY_AGREEMENT + KEY_DERIVATION | 先协商再派生 |
| 混合场景 | SIGN + ENCRYPT(不同密钥) | 签名和加密用不同密钥 |
我曾经犯过一个错误:在同一个密钥上同时设置了 SIGN 和 ENCRYPT。结果密钥被导出后,攻击者既能伪造签名,又能解密数据。从那以后,我坚持「一密钥一用途」的原则。
12.5 避坑指南
我曾经踩过的坑:
- 别把密钥用途设成「ALL」——虽然方便,但安全风险极高
- 密钥派生时,注意指定派生算法和盐值,否则每次派生结果一样
- 密钥协商后,一定要做「密钥确认」步骤,防止中间人攻击
- Android 版本不同,支持的用途组合有差异,记得做兼容性测试
12.6 知识体系总览
下面这张图是我自己整理的密钥用途知识体系,你可以对照着理解:
12.7 实战建议
最后,给你几个实在的建议:
- 先规划再动手:设计密钥体系时,先想清楚每个密钥的用途边界
- 用途越少越安全:能只签名就别加加密,能只加密就别加派生
- 版本兼容要留意:Android 6 以下不支持 KEY_AGREEMENT,8 以下不支持 KEY_DERIVATION
- 测试要覆盖:每个用途组合都要写单元测试,确保行为符合预期
嗯,关于密钥用途就聊这么多。记住一句话:密钥用途不是功能列表,而是安全边界。边界划得越清楚,系统就越安全。
公众号:蓝海资料掘金营,微信deep3321