Google Play 攻略

459 阅读5分钟

攻略大全

1. 粘贴攻略

1.1 关于代码合规

1.1.1 本地密钥存储不安全问题

1.1.2 包含隐式内部intent问题

1.1.3 所有网域允许传输明文流量问题

1.1.4 验证安全连接时接受用户证书问题

1.1.5 文件解压路径不安全问题

2. 造火箭攻略

3. 拧螺丝攻略

3.1 关于代码合规

3.1.1 本地密钥存储安全问题

3.1.1.1 AndroidKeystore密钥库系统介绍

AndroidKeystore系统是一个密钥库管理系统,谷歌设计这个系统的初衷应该是为了对标苹果的钥匙串KeyChain,有意思的是谷歌在Android4.0(API14)时便引入了KeyChain,但是并未提供详尽的说明文档,仅仅提供了一个API文档。

谷歌在Android4.3(API18)中引入了AndroidKeystore,在Android6.0(API23)中对AndroidKeystore进行了一波升级优化,目前来说应该是最安全的加解密方案。根据官方文档说明,原文对它的介绍如下:

利用 Android Keystore 系统,您可以在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入 Keystore 后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。

乍一看,这似乎是一个密钥保险箱,安全性高,可以把加密密钥存入其中,需要的时候再通过它用我们的密钥。可是!!这货的使用根本就不是个存储空间!!我们先来看看这东西大致的使用流程是怎样的,让大家直观的明白它的作用:

// step 1 通过密钥别名判断是否已有密钥

keyStore.containsAlias(keyAlias);

// step 2 没有密钥,生成一个
spec = new KeyGenParameterSpec.Builder(keyAlias, ...)...;
keyPairGenerator.initialize(spec);
keyPairGenerator.generateKeyPair();

// step 3 取出公钥,进行加密
publicKey = ((KeyStore.PrivateKeyEntry)entry).getCertificate().getPublicKey();
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipher.doFinal(data);

// step 4 取出私钥,进行解密
privateKey = ((KeyStore.PrivateKeyEntry)entry).getPrivateKey();
cipher.init(Cipher.DECRYPT_MODE, privateKey);
cipher.doFinal(data);

如此看来,AndroidKeystore根本就不是个密钥存储库。这和EncryptUtil这样的工具类几乎是一样的,先init,再encrypt,然后decrypt,俨然一个加解密工具。说白了,它在内部生成一个密钥,我们可以用它来加解密数据,并不能把我们自己生成的密钥送进去。

根据文档的介绍,它的大部分功能都只在Android6.0(API23)及以上支持,最低支持的Android4.3(API18)只提供RSA/ECB算法的使用。

3.1.1.2 密钥的检测与生成

AndroidKeystore是通过密钥的别名来进行加解密的,那么我们在使用之前必然是需要判断一下密钥是否存在的,AndroidKeystore提供了containsAlias()方法来完成密钥的检测,具体使用如下:

public boolean hasKey(String keyAlias) {
    try {
        // 加载一个AndroidKeyStore类型的KeyStore,貌似是定死的类型。
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        return keyStore.containsAlias(keyAlias);
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}

这里可以"AndroidKeyStore"用常量存一下,后面还会用到,避免魔法字符串。

如果密钥库中没有该别名的密钥,那么我们得让它生成一个。

public void buildKey(Context context, String keyAlias) {
    try{
        // 先获取密钥对生成器,采用RSA算法,AndroidKeyStore类型
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");

        // 加密算法的相关参数
        AlgorithmParameterSpec spec;
        
        // 密钥的有效起止时间,从现在到999年后,时间大家自己定
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        end.add(Calendar.YEAR, 999)

        // 生成加密参数,从Android6.0(API23)开始有所不同
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 根据密钥别名生成加密参数,提供加密和解密操作
            spec = new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)

                // SHA (Secure Hash Algorithm,译作安全散列算法) 是美国国家安全局 (NSA) 设计,美国国家标准与技术研究院 (NIST) 发布的一系列密码散列函数。 感兴趣的同学可以了解一下
                .setDigests(KeyProperties.DIGEST_SHA512)
                // 填充模式,一般RSA加密常用PKCS1Padding模式
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                // 限定密钥有效期起止时间
                .setCertificateNotBefore(start.getTime())
                .setCertificateNotAfter(end.getTime())
                .build();
        } else {
            // 相对于Android6.0(API23)的方式,这种稍显简单
            spec = new KeyPairGeneratorSpec.Builder(context.getApplicationContext())
                .setAlias(keyAlias)

                // 设置用于生成的密钥对的自签名证书的主题,X500Principal这东西不认识,资料真少,看的头大
                .setSubject(new X500Principal("CN=" + keyAlias))
                // 设置用于生成的密钥对的自签名证书的序列号,从BigInteger取即可
                .setSerialNumber(BigInteger.TEN)
                // 限定密钥有效期起止时间
                .setStartDate(start.getTime())
                .setEndDate(end.getTime())
                .build()
        }

        // 用加密参数初始化密钥对生成器,生成密钥对
        keyPairGenerator.initialize(spec);
        keyPairGenerator.generateKeyPair();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();

    }
}

3.1.1.3 加解密流程

加解密的流程非常相似,先加载KeyStore,再拿到公钥/私钥,最后进行加密/解密:

// 加密方法
public byte[] encrypt(Context context, String keyAlias, byte[] data) {
    if(!hasKey(keyAlias)) {
        buildKey(context, keyAlias);
    }

    try {

        // 获取"AndroidKeyStore"类型的KeyStore,加载
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);

        // 拿到密钥别名对应的Entry
        KeyStore.Entry entry = keyStore.getEntry(keyAlias, null);
        if (entry instanceof KeyStore.PrivateKeyEntry) {
        
            // 通过Entry拿到公钥对象(并不是真实的公钥,仅供加密方法使用)
            PublicKey publicKey = ((KeyStore.PrivateKeyEntry)entry).getCertificate().getPublicKey();

            // 使用"RSA/ECB/PKCS1Padding"模式进行加密
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnrecoverableEntryException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
    return null;

}



// 解密方法
public byte[] decrypt(Context context, String keyAlias, byte[] data) {

    if(!hasKey(keyAlias)) {
        buildKey(context, keyAlias);
    }

    try {

        // 获取"AndroidKeyStore"类型的KeyStore,加载
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);

        // 拿到密钥别名对应的Entry
        KeyStore.Entry entry = keyStore.getEntry(keyAlias, null);
        if (entry instanceof KeyStore.PrivateKeyEntry) {

            // 通过Entry拿到私钥对象(并不是真实的私钥,仅供解密方法使用)
            PrivateKey privateKey = ((KeyStore.PrivateKeyEntry)entry).getPrivateKey();
            
            // 使用"RSA/ECB/PKCS1Padding"模式进行解密
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        }

    } catch (IOException e) {
        e.printStackTrace();
    } catch (CertificateException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnrecoverableEntryException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
    return null;

}

3.1.1.4 常规用法

通常只把AndroidKeystore用于对密钥的加密,也就是说,建议将需要加密的数据使用对称加密算法(如AES)进行加密,而对称加密算法的密钥则由AndroidKeystore进行加密保护。通常用法如下:

// saveKey()方法用于将加密后的aesKey持久化,parseByte2Hex()方法用于将二进制转为十六进制,防止byte[]转String导致格式出错的问题。
saveKey(parseByte2Hex(encrypt(context, keyAlias, aesKey)));

// readKey()方法用于读取持久化的加密的aesKey,parseHex2Byte()方法用于将十六进制转为二进制,防止String转byte[]导致格式出错的问题。
aesKey = decrypt(context, key, parseHex2Byte(readKey()));

4. 复制攻略