Android加密基础:AES、RSA、密钥存储方案对比
做Android开发,尤其是涉及到用户敏感数据的时候,加密是绕不开的一关。我个人刚接触这块时也晕过——AES、RSA、还有各种KeyStore,到底选哪个?怎么搭才不出ANR?
说白了,加密不是万能药,但不用它,你的App就像没锁门的保险柜。这一章咱们就把Android上最常用的两种加密算法——AES和RSA——彻底理清楚,再聊聊密钥该怎么存才安全。
对称加密:AES
AES(Advanced Encryption Standard)是目前最主流的对称加密算法。什么叫对称?加密和解密用的是同一个密钥。速度快、适合加密大量数据,比如文件、图片、数据库字段。
Android上使用AES,我建议用AES/GCM/NoPadding模式。GCM模式自带认证标签,能防篡改。你想想,如果只加密不验证,坏人改了密文你都不知道。
核心参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 算法 | AES | 密钥长度128/192/256位,Android推荐256 |
| 工作模式 | GCM | 自带认证,不需要额外HMAC |
| 填充方式 | NoPadding | GCM内部处理,不额外填充 |
| IV | 12字节随机 | 每次加密必须不同,否则泄露明文模式 |
代码示例:AES-GCM加密与解密
// 加密
fun encryptAesGcm(plainText: ByteArray, key: SecretKey): ByteArray {
val iv = ByteArray(12).apply { SecureRandom().nextBytes(this) }
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.ENCRYPT_MODE, key, spec)
val cipherText = cipher.doFinal(plainText)
// 返回 iv + cipherText,解密时需要一起提供
return iv + cipherText
}
// 解密
fun decryptAesGcm(encryptedData: ByteArray, key: SecretKey): ByteArray {
val iv = encryptedData.copyOfRange(0, 12)
val cipherText = encryptedData.copyOfRange(12, encryptedData.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, key, spec)
return cipher.doFinal(cipherText)
}
看到没?加密时我把IV和密文拼在一起返回,解密时再切出来。这样省去了单独存储IV的麻烦。GCM的认证标签由内部自动校验,如果密文被篡改,doFinal会抛AEADBadTagException。
非对称加密:RSA
RSA是非对称加密的代表——公钥加密,私钥解密。速度比AES慢得多,但它能解决密钥分发问题。比如AES的密钥怎么安全传给对方?用RSA加密那个AES密钥,再传过去。
RSA在Android上的最佳实践
- 密钥长度:2048位(4096太慢,1024已不安全)
- 填充方案:必须用
RSA/ECB/OAEPWithSHA-256AndMGF1Padding,千万不要用PKCS1Padding(有已知漏洞) - 加密数据长度限制:OAEP模式下最多加密190字节(2048位密钥)。超出就报错
代码示例:RSA-OAEP加密
fun rsaEncrypt(data: ByteArray, publicKey: PublicKey): ByteArray {
val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data) // data长度不能超过190字节
}
密钥存储方案对比
密钥放哪?放SharedPreferences?那不叫加密,叫笑话。Android提供了几种安全存储方案,我根据项目经验排了个表:
| 方案 | 安全性 | 性能 | 适用场景 | 避坑点 |
|---|---|---|---|---|
| Android KeyStore | 极高(硬件隔离) | 中等 | RSA私钥、AES密钥 | API 23以上才支持硬件安全模块,低版本可能回退到软件实现 |
| EncryptedSharedPreferences | 高 | 快 | 小数据、、偏好设置 | 内部使用AES-256+GCM,但密钥仍托管在KeyStore |
| NDK + 硬编码密钥 | 低(可逆向) | 极快 | 不推荐,除非整包加固 | 我见过有人把密钥写死在so里,结果被IDA一把梭 |
| 远程密钥服务器 | 极高 | 依赖网络 | 动态密钥,需联网 | 有网络延迟,可能造成ANR,需异步处理 |
生成与使用KeyStore中的密钥
// 生成AES密钥存入KeyStore
fun generateAesKeyInKeyStore(alias: String): SecretKey {
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
)
keyGenerator.init(
KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
)
return keyGenerator.generateKey()
}
// 获取已存在的密钥
fun getKeyFromKeyStore(alias: String): SecretKey? {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
return keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry
}
注意:setKeySize(256)在部分Android 10以下设备上可能不支持,建议做运行时检测。如果设备不支持256位,可以降级到128位,但要记录日志。
知识体系框架
下面这张图把本章的核心脉络串起来了。你能看到AES和RSA各自负责什么,密钥怎么存,以及组合起来的样子。
组合策略:RSA + AES + KeyStore
一个一个讲完了,来一个实战组合拳。我最近重构的一个金融类App就是这样干的:
- App启动时,从KeyStore取出或生成AES密钥(别名"user_aes_key")
- 登录时,服务器下发RSA公钥(证书绑定防中间人)
- 用RSA公钥加密本地AES密钥,密文上传服务器
- 服务器用私钥解密,拿到AES密钥用于后续通信
- 本地数据直接用AES-GCM加密存储,密钥全程不离开KeyStore
至于解密,我建议把解密操作做成挂起函数,用Dispatchers.IO调度。这样既不会阻塞UI,又能利用多核。
常见误区总结
- 把密钥硬编码在代码里——反编译一下全暴露,形同虚设
- AES使用ECB模式——同样的明文块产生同样的密文块,照片加密后还能看出轮廓
- RSA直接加密大块数据——超过密钥长度限制直接抛异常,正确的做法是混合加密
- 忽略KeyStore的异常回退——部分老旧设备不支持硬件KeyStore,需要降级到软件实现,但必须记录并告警