现在比较出名的开源的加密聊天 IM 是 signal, 在 GitHub 开源了很久, 而且已经有过万的 stars.
其本地消息加密有一套完善的机制, 上层我们称为 MasterScrect.
说一下使用的这套 materscrect 这套加密
- // 获取加密信息
- public static MasterSecret getMasterSecret(Context context, String passphrase)
- throws InvalidPassphraseException {
- try {
- // 取回整一串保存在本地的加密钥和口令
- byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
- // 取回口令的盐
- byte[] macSalt = retrieve(context, "mac_salt");
- // 取回迭代次数
- int iterations = retrieve(context, "passphrase_iterations", 100);
- // 验证口令取回加密钥
- byte[] encryptedMasterSecret = verifyMac(macSalt, iterations, encryptedAndMacdMasterSecret, passphrase);
- // 取回加密钥的盐
- byte[] encryptionSalt = retrieve(context, "encryption_salt");
- // 解密密钥 (密码为空)
- byte[] combinedSecrets = decryptWithPassphrase(encryptionSalt, iterations, encryptedMasterSecret, passphrase);
- // 加密密钥 (密钥串中 1~15 个字符)
- byte[] encryptionSecret = Util.split(combinedSecrets, 16, 20)[0];
- // 口令密钥 (密钥串中 16~20 个字符)
- byte[] macSecret = Util.split(combinedSecrets, 16, 20)[1];
- // 封装密钥和口令
- return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
- new SecretKeySpec(macSecret, "HmacSHA1"));
- } catch (GeneralSecurityException e) {
- Log.w("keyutil", e);
- return null; //XXX
- } catch (IOException e) {
- Log.w("keyutil", e);
- return null; //XXX
- }
- }
再看一下生成密钥的函数, 在用户登录的时候会生成一次
- public static MasterSecret generateMasterSecret(Context context, String passphrase) {
- try {
- // 生成密钥
- byte[] encryptionSecret = generateEncryptionSecret();
- // 生成口令
- byte[] macSecret = generateMacSecret();
- // 合成字符串密钥
- byte[] masterSecret = Util.combine(encryptionSecret, macSecret);
- // 生成加密盐
- byte[] encryptionSalt = generateSalt();
- // 添加迭代次数
- int iterations = generateIterationCount(passphrase, encryptionSalt);
- // 添加加密盐后加密字符串密钥
- byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, masterSecret, passphrase);
- // 添加口令盐
- byte[] macSalt = generateSalt();
- // 添加口令盐后再加密
- byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, passphrase);
- // 写入 SharePreference
- save(context, "encryption_salt", encryptionSalt);
- save(context, "mac_salt", macSalt);
- save(context, "passphrase_iterations", iterations);
- save(context, "master_secret", encryptedAndMacdMasterSecret);
- save(context, "passphrase_initialized", true);
- // 通过 AES 加密密钥, 通过哈希口令散列算法加密口令密钥
- return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
- new SecretKeySpec(macSecret, "HmacSHA1"));
- } catch (GeneralSecurityException e) {
- Log.w("keyutil", e);
- return null;
- }
- }
1. 随机生成 AES128 位密钥
- private static byte[] generateEncryptionSecret() {
- try {
- KeyGenerator generator = KeyGenerator.getInstance("AES");
- generator.init(128);
- SecretKey key = generator.generateKey();
- return key.getEncoded();
- } catch (NoSuchAlgorithmException ex) {
- Log.w("keyutil", ex);
- return null;
- }
- }
2. 使用 HmacSHA1(哈希口令散列算法) 生成随机口令
- private static byte[] generateMacSecret() {
- try {
- KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
- return generator.generateKey().getEncoded();
- } catch (NoSuchAlgorithmException e) {
- Log.w("keyutil", e);
- return null;
- }
- }
3.combine 是多个字符串合并, 只是添加到末尾
4. 生成盐, 盐是 SHA1PRNG 生成 16 位随机字符, 添加盐, 是添加了冗余来添加混淆复杂度
- private static byte[] generateSalt() throws NoSuchAlgorithmException {
- SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
- byte[] salt = new byte[16];
- random.nextBytes(salt);
- return salt;
- }
5. 通过不同机型的性能来确定迭代的次数, 最少迭代 100 次, 之后通过计算来确定 CPU 运算能力.
- ```
- private static int generateIterationCount(String passphrase, byte[] salt) {
- int TARGET_ITERATION_TIME = 50; //ms
- int MINIMUM_ITERATION_COUNT = 100; //default for low-end devices
- int BENCHMARK_ITERATION_COUNT = 10000; //baseline starting iteration count
- try {
- PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, BENCHMARK_ITERATION_COUNT);
- SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
- // 尝试迭代一些字符, 来确定 CPU 运算能力
- long startTime = System.currentTimeMillis();
来源: https://juejin.im/entry/5c6e6cf0518825622d74b678