加密与密钥管理:对称加密、非对称加密、哈希算法、密钥存储安全、随机数生成

各位同学,今天我们来聊聊 Android 安全里最硬核、也最容易踩坑的一块——加密与密钥管理。说实话,我见过太多应用因为加密没做好,或者密钥直接硬编码在代码里,导致用户数据被拖库的惨案。我自己早年也犯过类似的错误,后来被安全审计怼得无地自容。嗯,今天就把这些血泪教训一并分享给你们。

一、对称加密:又快又稳,但密钥怎么传?

对称加密,说白了就是加密和解密用同一把钥匙。你想想看,这就像你家大门钥匙,你自己能开门,别人捡到也能开。所以它的优点是快,适合加密大量数据;缺点嘛,密钥分发是个大麻烦。

在 Android 里,我常用的对称加密算法是 AES,尤其是 AES-GCM 模式。为什么选 GCM?因为它自带认证标签,能防止数据被篡改。我以前用过 CBC 模式,结果被 padding oracle 攻击搞得焦头烂额……后来就再也不敢用了。

核心建议: 对称加密首选 AES-256-GCM,不要用 ECB 模式,不要自己拼凑加密方案。
// 正确示例: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
}
小提示: IV(初始化向量)每次加密都要随机生成,不要固定!我见过有人把 IV 写死在代码里,那跟没加密没啥区别。

二、非对称加密:密钥分发不用愁,但慢

非对称加密,就是公钥加密、私钥解密。公钥随便发,私钥自己藏好。这解决了对称加密的密钥分发问题,但性能差很多。你想想看,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)
}
警告: 非对称加密不要用来加密大量数据!RSA 最多加密密钥长度对应的字节数(比如 2048 位 RSA 最多加密 256 字节)。超出就报错。

三、哈希算法:不是加密,是摘要

哈希算法,很多人误以为它是加密。其实不是。哈希是单向的,你从哈希值推不出原文。它的用途是完整性校验和密码存储。

我个人习惯用 SHA-256SHA-3。MD5 和 SHA-1 已经不安全了,别再用。但要注意,直接对密码做哈希是不安全的——因为彩虹表攻击。所以一定要加盐(salt)。

我曾经在一个项目里看到有人直接用 MD5 存密码,我当时就震惊了。后来我给他们改成了 bcryptArgon2,这些是专门为密码哈希设计的算法,自带盐和迭代次数,能有效抵抗暴力破解。

// 正确示例:使用 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()
}
避坑指南: 我曾经把密钥存在应用的私有目录里,以为别人读不到。结果发现备份工具能直接读取……后来改用 Keystore 才安心。记住:密钥永远不要离开安全硬件。

五、随机数生成:不随机,就不安全

随机数在加密里无处不在——生成密钥、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 密钥分发困难 适合大量数据 非对称加密 RSA-OAEP 公钥加密,私钥解密 适合小数据 哈希算法 SHA-256 / bcrypt 单向摘要 密码存储需加盐 密钥存储安全 Android Keystore 硬件安全模块 随机数生成 SecureRandom 不可预测 混合加密 非对称传密钥 对称加密数据

七、总结与最佳实践

好了,我们来捋一捋今天讲的核心要点:

  • 对称加密:用 AES-256-GCM,IV 必须随机,密钥用 Keystore 存。
  • 非对称加密:用 RSA-OAEP,只加密小数据,配合对称加密做混合方案。
  • 哈希算法:用 SHA-256 或 bcrypt,密码存储必须加盐,迭代次数要够。
  • 密钥存储:永远用 Android Keystore,别把密钥写在代码或文件里。
  • 随机数:只用 SecureRandom,别用 Math.random() 或时间戳。

我个人习惯在项目初期就把加密方案定下来,而不是等安全审计时再补。因为一旦数据已经用错误的方式加密了,迁移成本非常高。你想想看,用户的数据已经用弱算法加密存到数据库了,你要怎么升级?只能让用户重新登录、重新加密——这体验太差了。

最后送大家一句话:加密不难,难的是密钥管理。 把密钥管好了,你的加密体系就成功了一大半。

核心原则: 永远不要自己发明加密算法,永远不要硬编码密钥,永远使用经过验证的标准库。

公众号:蓝海资料掘金营,微信deep3321