Android 安全模型
Android 的安全靠三层:应用沙箱隔离进程、权限体系控制能力、加密保护数据。前两层是系统给的,第三层要开发者自己做好。
权限管的是谁能进来,加密管的是拿走了也读不懂。
常见误区:把 token 明文存 SharedPreferences、日志里打密码、忽略证书校验。安全是系统工程,先从最容易被翻出来的数据入手。
Keystore 与密钥管理
Android Keystore 把密钥放在系统级保险箱里,App 只能使用不能导出。用它生成 AES 密钥,再配合设备锁屏解锁,安全性直接拉满。
val generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
generator.init(KeyGenParameterSpec.Builder(
"app_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build())
val key = generator.generateKey()
- 硬件隔离密钥不出安全芯片。
- 不可导出拿不到密钥原文。
- 解锁绑定可要求设备解锁可用。
密钥别名用固定字符串,别把密钥对象序列化存文件。Keystore 密钥删了就没了,迁移要设计好再删。
AES/GCM 数据加密
AES-GCM 是推荐的对称加密:一次加密同时保证机密和完整性。每次加密生成随机 IV,IV 与密文一起存,解密时取回来。
fun encrypt(key: SecretKey, plain: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, key)
val iv = cipher.iv
val encrypted = cipher.doFinal(plain)
return iv + encrypted // IV 拼在密文前面
}
fun decrypt(key: SecretKey, data: ByteArray): ByteArray {
val iv = data.copyOfRange(0, 12)
val body = data.copyOfRange(12, data.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, iv))
return cipher.doFinal(body)
}
IV 每次必须不同,否则相同明文会出相同密文,泄露规律。别自己发明加密算法,用标准库的 AES/GCM 就够安全。
安全存储与偏好
Android 6 之后有 EncryptedSharedPreferences,键值整体加密;Jetpack Security 库的 EncryptedFile 加密整个文件。token、密钥、敏感配置都放这里。
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
context, "secure_prefs", masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
加密存储不是万能:根目录下攻破的设备任何保护都有限。但加密能挡住常规的数据窃取和备份泄露,成本低收益高,默认就该用。
密钥与数据分开存放:加密后的密文可以进 SharedPreferences 或数据库,但密钥永远留在 Keystore。每次加密都生成随机 IV 并随密文保存,解密时取回;验证完整性用 GCM 的校验能力,解密失败说明数据被篡改或密钥不匹配,此时应当清空本地敏感数据并引导用户重新登录,而不是把错误数据继续用下去,同时把异常上报到监控,观察是否有人批量触发解密失败。备份恢复也要考虑:Keystore 密钥随设备走,换机时通过加密迁移或重新登录来重建密钥,别把密文拷过去却丢了钥匙。
还有一点:加密不是越复杂越好,标准库的 AES/GCM 足够;把算法参数写死在代码里,避免运行时配置被攻击者改写。
HTTPS 与证书
网络传输必须 HTTPS。默认 OkHttp 就校验证书,别关掉校验去兼容自签名证书。生产环境用受信任 CA,测试环境单独打 debug 包再放开。
val client = OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustManager)
.build()
证书固定(Certificate Pinning)适合高安全场景:把服务器证书指纹写进代码,防止中间人。注意证书轮换时双固定新旧指纹,避免线上事故。
实战:敏感数据保护
把登录 token 从明文偏好换成 Keystore 加密:Keystore 生成 AES 密钥,token 用 AES/GCM 加密后存 EncryptedSharedPreferences,读的时候解密。
fun saveToken(token: String) {
val encrypted = encrypt(keystoreKey, token.toByteArray())
securePrefs.edit().putString("token", Base64.encodeToString(
encrypted, Base64.NO_WRAP)).apply()
}
同时做三件事:明文偏好里不存 token、日志不打敏感值、接口用 HTTPS。数据脱敏和最小化——不必要收集的信息,别收集。
灰度发布时把加密模块作为独立开关,先小范围验证再全量;一旦发现密钥或算法问题,能在不停服的前提下滚动修复,别让一次加密事故卡住整个版本。
小结
Android 安全三板斧:Keystore 管密钥、AES/GCM 加密数据、EncryptedSharedPreferences 安全落盘;传输走 HTTPS,高危场景加证书固定。权限给最小、日志别留痕、数据能不加就不加。安全不是某一行代码,是每一处都不留口子。