Android 安全存储——Android Keystore
Android 安全存储——Android Keystore
larkKeyStore 其实并不是 Android 独有,它是 JCA(Java 密码学架构)里的密钥库门面类,实现由 Provider 通过 KeyStoreSpi 提供。在 Android 上,系统注册了一个名为 AndroidKeyStore 的 Provider,把 KeyStoreSpi 接到了硬件密钥库上。它支持 AndroidKeyStore、AndroidCAStore、BKS、PKCS12 这几种 type。
开局常量配置
可以声明配置密钥别名、指定加密算法组合和初始化向量的长度(IV)。
- 密钥名要具有唯一性,最好带个版本号,方便以后处理些东西。
1 | private const val KEY_ALIAS = "lime_token_key_v1" |
算法字符串是要按照 算法 / 模式 / 填充 的格式来配置,通常是 "AES/GCM/NoPadding",AES 是一种对称加密算法,GCM 是工作模式,NoPadding 是无填充。GCM 是一种认证加密模式,不仅可以保证数据的机密性,还能保证数据的完整性(也就是防篡改)。
IV 的作用是用同样的明文和同一把密钥,让每次加密出来的密文都不一样。
打开密钥库并生成密钥
向系统索要一个密钥库对象
需要先指定密钥库类型,这里选 "AndroidKeyStore" 就行,数据由系统硬件保管,不用密码,所以适合用来加密本地数据、存令牌。但这时它还没有和系统的密钥服务连上,硬件密钥库又不需要从文件读数据、也不需要密码,所以打开密钥库时参数传 null 即可。
1 | val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } |
在密钥库里生成密钥
要一个密钥生成器,填上你要的算法和提供者。
1 | generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") |
用 KeyGenParameterSpec 说明用途和规格,
1 | generator.init( |
生成 key 引用
1 | return generator.generateKey() |
附一张表供自查:
| Provider(提供者) | 它提供的 KeyStore 类型 |
|---|---|
AndroidKeyStore |
AndroidKeyStore |
AndroidOpenSSL(Conscrypt) |
AndroidCAStore |
BC(BouncyCastle) |
BKS、PKCS12(还有 BouncyCastle 等别名) |
加密
创建 Cipher,获取加密工具。
1 | val cipher = Cipher.getInstance(TRANSFORMATION)// 算法 / 模式 / 填充 |
初始化,指定密钥与选择加密操作。
1 | cipher.init(Cipher.ENCRYPT_MODE, SecretKey)// 加密这把密钥 |
取出 IV ,并执行加密。
对了,还要注意 IV 是在 init() 那一刻由系统生成的,切记要在 doFinal 前取出来,不然的话加密结束 Cipher 对象的状态会被重置。
1 | val iv = cipher.iv |
最后是组装,把 IV 和密文拼接,再转成 Base64 字符串
1 | Base64.encodeToString(iv + encrypted, Base64.NO_WRAP) |
这样加密之后就是这个形式啦。
Base64( [IV 12 字节] [密文] [校验标签 16 字节] )
解密
解密当然是加密的逆操作,和加密时反着来。
把 Base64 字符串还原成明文,把存进去的字符串变回字节数组。
1 | val raw = Base64.decode(encoded, Base64.NO_WRAP) |
解铃还须系铃人,按约定的格式拆开后,再有请 Cipher 😄
1 | val iv = raw.copyOfRange(0, IV_LENGTH) |
解密时要用原来的 IV ,需手动传,用 GCMParameterSpec 装 GCM 加解密需要的两个参数 。即校验标签长度和 IV。
1 | cipher.init(Cipher.DECRYPT_MODE, SecretKey, GCMParameterSpec(128, iv)) |
没啥可说的,依旧逆操作,然后你就解密成功了!
1 | String(cipher.doFinal(body), Charsets.UTF_8) |
附
此由 AI 生成 👇,未经核实研究,请辩证看待。
| 场景 | 用什么 | 密钥存哪 |
|---|---|---|
| 加密本地小数据(token、配置、缓存)——你这个项目 | AES/GCM/NoPadding |
AndroidKeyStore(硬件,不可导出) |
| 加密大文件 | 同样 AES/GCM,但走流式或分段 | AndroidKeyStore |
| 用户设了密码,要用密码加密数据 | AES/GCM + PBKDF2 从密码派生密钥 |
不需要密钥库,密钥每次从密码算出来 |
| 要跨设备/跨平台解密(换手机还能解、后端也能解) | AES/GCM |
不能用 AndroidKeyStore,密钥必须能导出或由密码派生 |
| 和后端交换加密数据 | AES/GCM 加密数据 + RSA/OAEP(或 ECDH)传密钥 |
后端公钥 + 本地随机密钥 |
| 网络传输 | 不要自己加密 | 交给 HTTPS/TLS |
| 存密码(要能验证,不能还原) | PBKDF2WithHmacSHA256 + 随机 salt |
不适用(不可逆) |
| 校验数据有没有被改(不保密) | HmacSHA256(需要防伪造)或 SHA-256(只防意外损坏) |
HMAC 的密钥放 AndroidKeyStore |
| 签名/身份验证 | Signature.getInstance("SHA256withECDSA") 或 SHA256withRSA |
AndroidKeyStore(KeyPairGenerator) |
| 必须解锁手机才能用 | 同上 + setUserAuthenticationRequired(true) |
AndroidKeyStore |

