IM 使用的加密私聊到本地

1,565 阅读2分钟
原文链接: xiaozhuanlan.com

现在比较出名的开源的加密聊天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();