加密与密钥管理:对称加密、非对称加密、哈希算法、密钥存储安全、随机数生成
大家好,我是你们的老朋友。今天我们来聊聊 Android 安全里最核心、也最容易踩坑的一块——加密与密钥管理。
说实话,我见过太多应用,功能做得花里胡哨,结果加密这块一塌糊涂。密钥硬编码在代码里、随机数用 Random 而不是 SecureRandom、哈希不加盐……这些坑我几乎都踩过一遍。今天咱们就把这些事彻底捋清楚。
1. 对称加密:又快又简单,但密钥怎么藏?
对称加密,说白了就是加密和解密用同一把钥匙。你想想看,就像你家大门,钥匙只有一把,开门关门都用它。
Android 里最推荐的是 AES,尤其是 AES-GCM 模式。为什么?因为它自带认证,能防篡改。我早期用过 CBC 模式,结果被 padding oracle 攻击搞得焦头烂额……后来就再也不敢用了。
// 正确的 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;
}
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());
3. 哈希算法:不是加密,是「指纹」
哈希算法,很多人误以为它是加密。其实不是。哈希是单向的,你没法从哈希值反推出原文。它更像是一个「数字指纹」。
Android 里推荐用 SHA-256 或 SHA-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;
}
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();
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(),它会阻塞等待足够的熵。
知识体系总览
下面这张图,是我自己整理的加密与密钥管理知识体系。你可以把它当作一个检查清单,开发时对照着看。
总结一下
加密这件事,说白了就是「钥匙」和「锁」的博弈。钥匙要藏好,锁要够结实。我见过太多应用,加密算法选得没问题,结果密钥直接写在代码里——那还不如不加密。
嗯,这里要注意:没有绝对的安全,只有相对的安全。我们能做的,就是让攻击者的成本远高于收益。用 Android Keystore 存密钥、用 SecureRandom 生成随机数、用 AES-GCM 加密数据、用加盐哈希保护密码——这些做到了,你的应用就已经超过了 90% 的同行。
公众号:蓝海资料掘金营,微信deep3321