加密与密钥管理:对称加密、非对称加密、哈希算法、密钥存储安全、随机数生成
各位同学,今天我们来聊聊 Android 安全里最硬核、也最容易踩坑的一块——加密与密钥管理。说实话,我见过太多应用因为加密没做好,或者密钥直接硬编码在代码里,导致用户数据被拖库的惨案。我自己早年也犯过类似的错误,后来被安全审计怼得无地自容。嗯,今天就把这些血泪教训一并分享给你们。
一、对称加密:又快又稳,但密钥怎么传?
对称加密,说白了就是加密和解密用同一把钥匙。你想想看,这就像你家大门钥匙,你自己能开门,别人捡到也能开。所以它的优点是快,适合加密大量数据;缺点嘛,密钥分发是个大麻烦。
在 Android 里,我常用的对称加密算法是 AES,尤其是 AES-GCM 模式。为什么选 GCM?因为它自带认证标签,能防止数据被篡改。我以前用过 CBC 模式,结果被 padding oracle 攻击搞得焦头烂额……后来就再也不敢用了。
// 正确示例:AES-GCM 加密
fun encryptAesGcm(plaintext: ByteArray, key: SecretKey): ByteArray {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = ByteArray(12) // 推荐 12 字节 IV
SecureRandom().nextBytes(iv)
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.ENCRYPT_MODE, key, spec)
val ciphertext = cipher.doFinal(plaintext)
// 返回 IV + 密文,解密时需要一起传入
return iv + ciphertext
}
二、非对称加密:密钥分发不用愁,但慢
非对称加密,就是公钥加密、私钥解密。公钥随便发,私钥自己藏好。这解决了对称加密的密钥分发问题,但性能差很多。你想想看,RSA 加密几百字节的数据就要几十毫秒,要是加密一个大文件,那得等到天荒地老。
所以实际项目中,我们通常用 混合加密:用非对称加密来传输对称密钥,然后用对称加密来加密实际数据。我在做一个即时通讯应用时就是这么干的——客户端用服务器的公钥加密一个临时生成的 AES 密钥,服务器用私钥解密得到密钥,后续通信都用 AES 加密。
Android 上推荐用 RSA-OAEP,不要用 RSA-PKCS1v1.5,那个有 Bleichenbacher 攻击漏洞。嗯,这个坑我也踩过。
// 正确示例:RSA-OAEP 加密
fun encryptRsaOaep(plaintext: ByteArray, publicKey: PublicKey): ByteArray {
val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(plaintext)
}
三、哈希算法:不是加密,是摘要
哈希算法,很多人误以为它是加密。其实不是。哈希是单向的,你从哈希值推不出原文。它的用途是完整性校验和密码存储。
我个人习惯用 SHA-256 或 SHA-3。MD5 和 SHA-1 已经不安全了,别再用。但要注意,直接对密码做哈希是不安全的——因为彩虹表攻击。所以一定要加盐(salt)。
我曾经在一个项目里看到有人直接用 MD5 存密码,我当时就震惊了。后来我给他们改成了 bcrypt 或 Argon2,这些是专门为密码哈希设计的算法,自带盐和迭代次数,能有效抵抗暴力破解。
// 正确示例:使用 PBKDF2 进行密码哈希
fun hashPassword(password: String, salt: ByteArray): ByteArray {
val spec = PBEKeySpec(password.toCharArray(), salt, 10000, 256)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
return factory.generateSecret(spec).encoded
}
四、密钥存储安全:别把钥匙放在门口垫子下
密钥存储是加密体系里最薄弱的一环。你算法再强,密钥被人拿走了,一切白搭。我见过最离谱的做法是把密钥写在 SharedPreferences 里,或者硬编码在 Java 代码中。这就像把家门钥匙放在门口垫子下——小偷一翻就找到了。
Android 上正确的做法是使用 Android Keystore 系统。它把密钥存储在硬件安全模块(TEE 或 StrongBox)中,应用只能使用密钥,不能导出密钥。即使设备被 root,攻击者也拿不到密钥材料。
// 正确示例:在 Android Keystore 中生成 AES 密钥
fun generateAesKeyInKeystore(alias: String) {
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
)
val spec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
keyGenerator.init(spec)
keyGenerator.generateKey()
}
五、随机数生成:不随机,就不安全
随机数在加密里无处不在——生成密钥、IV、盐、nonce,都需要随机数。如果随机数不够随机,攻击者就能预测你的密钥。我见过一个案例,有人用 Math.random() 生成密钥,结果被暴力破解了。为什么?因为 Math.random() 是伪随机数,种子可预测。
在 Android 上,一定要用 java.security.SecureRandom。它从系统熵源(比如传感器噪声、触摸事件)获取随机性,不可预测。而且从 Android 4.4 开始,SecureRandom 已经默认使用 /dev/urandom,安全性有保障。
// 正确示例:生成安全的随机数
fun generateSecureRandomBytes(length: Int): ByteArray {
val random = SecureRandom()
val bytes = ByteArray(length)
random.nextBytes(bytes)
return bytes
}
Random 类!不要用当前时间戳作为种子!这些都是安全漏洞。
六、知识体系总览
下面这张图总结了加密与密钥管理的核心知识结构,你可以把它当作一张地图,随时回来对照。
七、总结与最佳实践
好了,我们来捋一捋今天讲的核心要点:
- 对称加密:用 AES-256-GCM,IV 必须随机,密钥用 Keystore 存。
- 非对称加密:用 RSA-OAEP,只加密小数据,配合对称加密做混合方案。
- 哈希算法:用 SHA-256 或 bcrypt,密码存储必须加盐,迭代次数要够。
- 密钥存储:永远用 Android Keystore,别把密钥写在代码或文件里。
- 随机数:只用 SecureRandom,别用 Math.random() 或时间戳。
我个人习惯在项目初期就把加密方案定下来,而不是等安全审计时再补。因为一旦数据已经用错误的方式加密了,迁移成本非常高。你想想看,用户的数据已经用弱算法加密存到数据库了,你要怎么升级?只能让用户重新登录、重新加密——这体验太差了。
最后送大家一句话:加密不难,难的是密钥管理。 把密钥管好了,你的加密体系就成功了一大半。
公众号:蓝海资料掘金营,微信deep3321