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

大家好,我是你们的老朋友。今天我们来聊聊 Android 安全里最核心、也最容易踩坑的一块——加密与密钥管理。

说实话,我见过太多应用,功能做得花里胡哨,结果加密这块一塌糊涂。密钥硬编码在代码里、随机数用 Random 而不是 SecureRandom、哈希不加盐……这些坑我几乎都踩过一遍。今天咱们就把这些事彻底捋清楚。

1. 对称加密:又快又简单,但密钥怎么藏?

对称加密,说白了就是加密和解密用同一把钥匙。你想想看,就像你家大门,钥匙只有一把,开门关门都用它。

Android 里最推荐的是 AES,尤其是 AES-GCM 模式。为什么?因为它自带认证,能防篡改。我早期用过 CBC 模式,结果被 padding oracle 攻击搞得焦头烂额……后来就再也不敢用了。

核心建议: 用 AES-256-GCM,不要自己拼凑 IV 和加密模式。
// 正确的 AES-GCM 加密示例
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

public byte[] encryptAESGCM(byte[] plaintext, SecretKey key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    byte[] iv = new byte[12]; // GCM 推荐 12 字节 IV
    SecureRandom.getInstanceStrong().nextBytes(iv);
    GCMParameterSpec spec = new GCMParameterSpec(128, iv);
    cipher.init(Cipher.ENCRYPT_MODE, key, spec);
    byte[] ciphertext = cipher.doFinal(plaintext);
    // 返回 IV + 密文,解密时拆开
    byte[] result = new byte[iv.length + ciphertext.length];
    System.arraycopy(iv, 0, result, 0, iv.length);
    System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
    return result;
}
我的习惯: IV 一定要随机生成,而且每次加密都要不同。我见过有人写死 IV,那跟没加密差不多。

2. 非对称加密:公钥随便给,私钥藏好

非对称加密,就是一对钥匙——公钥和私钥。公钥可以公开,私钥必须保密。这就像信箱,谁都能往里面投信(公钥加密),但只有你有钥匙能打开(私钥解密)。

Android 里常用 RSA,但要注意:RSA 不适合加密大量数据。我一般用它来加密对称密钥,然后用对称加密处理实际数据。这叫「混合加密」,业界标准做法。

// RSA 加密对称密钥的典型用法
import java.security.KeyPair;
import java.security.KeyPairGenerator;

KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048); // 至少 2048 位
KeyPair keyPair = generator.generateKeyPair();

// 公钥加密对称密钥
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] encryptedKey = cipher.doFinal(aesKey.getEncoded());
避坑指南: 我曾经见过有人用 RSA 直接加密大文件,结果性能惨不忍睹。记住:RSA 只适合加密小数据(比如密钥),大数据交给 AES。

3. 哈希算法:不是加密,是「指纹」

哈希算法,很多人误以为它是加密。其实不是。哈希是单向的,你没法从哈希值反推出原文。它更像是一个「数字指纹」。

Android 里推荐用 SHA-256SHA-512。MD5 和 SHA-1 已经不安全了,别再用。

但光用哈希还不够。你想想看,如果两个用户密码相同,哈希值也一样,那攻击者一看就知道。所以我们要加盐(salt)。

// 加盐哈希的正确姿势
import java.security.MessageDigest;
import java.security.SecureRandom;

public byte[] hashWithSalt(String password) throws Exception {
    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[16];
    random.nextBytes(salt);
    
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    digest.update(salt);
    byte[] hash = digest.digest(password.getBytes("UTF-8"));
    
    // 返回 salt + hash,验证时拆开
    byte[] result = new byte[salt.length + hash.length];
    System.arraycopy(salt, 0, result, 0, salt.length);
    System.arraycopy(hash, 0, result, salt.length, hash.length);
    return result;
}
重要: 每个用户的盐都要不同,而且盐要足够长(至少 16 字节)。我习惯用 32 字节。

4. 密钥存储安全:别把钥匙放在门口垫子下

密钥存哪?这是个大问题。很多人直接把密钥写在代码里,或者放在 SharedPreferences 里。这就像把家门钥匙放在门口垫子下——太容易被发现了。

Android 提供了 Android Keystore 系统,密钥存储在硬件安全模块(TEE)里,应用拿不到明文。这才是正道。

// 使用 Android Keystore 生成并存储密钥
import java.security.KeyStore;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

KeyGenerator keyGenerator = KeyGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
    new KeyGenParameterSpec.Builder("my_key_alias",
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .setKeySize(256)
        .build());
SecretKey key = keyGenerator.generateKey();
我的经验: 用 Keystore 生成的密钥,你只能通过别名引用它,永远拿不到它的明文。这样即使应用被逆向,密钥也安全。

5. 随机数生成:别用 Random,用 SecureRandom

随机数看似简单,但坑最多。Java 的 java.util.Random 是伪随机数生成器,可预测。如果你用它生成密钥或 IV,攻击者能猜出来。

一定要用 java.security.SecureRandom。它从系统熵源获取随机数,不可预测。

// 正确的随机数生成
SecureRandom secureRandom = SecureRandom.getInstanceStrong();
byte[] keyBytes = new byte[32]; // 256 位密钥
secureRandom.nextBytes(keyBytes);
我曾经踩过的坑: 有一次我用 new SecureRandom() 在模拟器上测试,结果每次生成的随机数都一样。后来发现模拟器熵源不足。解决方案是用 SecureRandom.getInstanceStrong(),它会阻塞等待足够的熵。

知识体系总览

下面这张图,是我自己整理的加密与密钥管理知识体系。你可以把它当作一个检查清单,开发时对照着看。

加密与密钥管理 对称加密 AES-256-GCM IV 随机生成 避免 CBC 模式 非对称加密 RSA-2048 以上 OAEP 填充 混合加密 哈希算法 SHA-256/512 加盐处理 避免 MD5/SHA-1 密钥存储 Android Keystore TEE 硬件隔离 禁止硬编码 随机数生成 SecureRandom getInstanceStrong() 避免 java.util.Random 核心原则:密钥永不离开 Keystore,随机数用 SecureRandom,哈希必加盐 对称加密用 AES-GCM,非对称用 RSA-OAEP,混合加密是王道

总结一下

加密这件事,说白了就是「钥匙」和「锁」的博弈。钥匙要藏好,锁要够结实。我见过太多应用,加密算法选得没问题,结果密钥直接写在代码里——那还不如不加密。

嗯,这里要注意:没有绝对的安全,只有相对的安全。我们能做的,就是让攻击者的成本远高于收益。用 Android Keystore 存密钥、用 SecureRandom 生成随机数、用 AES-GCM 加密数据、用加盐哈希保护密码——这些做到了,你的应用就已经超过了 90% 的同行。

最后送大家一句话: 加密不是功能,是态度。你对待密钥的态度,决定了你的应用能走多远。

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