在Android应用中存储凭据

时间:2017-03-02 16:32:43

标签: android encryption root credentials android-keystore

我们如何安全地存储凭据数据以访问Android应用中的smtp-server?这些数据是常量,只有开发人员应该知道它们。目前它们存储在代码中,但这不安全,因为可以通过反编译来看到它们。

是否可以将Android Keystore System用于此目的以及如何使用?最重要的是,Android Keystore在root设备上是否有效?

3 个答案:

答案 0 :(得分:8)

在Android应用程序中,您可以将数据存储在SharedPreferences中,但由于此数据实际存储在文件中,因此任何对手机具有root访问权限的人都可以访问它。这意味着如果要存储凭据或任何其他敏感数据,则会发生安全漏洞。

为了避免其他人以纯文本格式查看此数据,解决方案是在存储数据之前加密数据。从API 18开始,Android推出了KeyStore,它能够存储用于加密和解密数据的密钥。

问题直到API 23导致您无法在KeyStore中存储AES密钥,因此最可靠的加密密钥是使用私钥和公钥的RSA。

所以我提出的解决方案是:

适用于23以下的API

  • 生成RSA私钥和公钥并将其保存在KeyStore中,生成AES密钥,使用RSA公钥对其进行加密,并将其保存到SharedPreferences。
  • 每次需要使用AES密钥在SharedPreferences中保存加密数据时,您将从SharedPreferences获取加密的AES密钥,使用RSA私钥对其进行解密,并使用已解密的AES密钥加密要保存到SharedPreferences的数据。
  • 要解密数据,流程几乎相同,从SharedPreferences获取加密的AES密钥,使用RSA私钥解密,从您要解密的SharedPreferences获取加密数据,并使用解密的AES密钥对其进行解密。

适用于API 23及更高版本

  • 只需在KeyStore中生成并存储AES密钥,并在需要进行数据加密/解密时访问它。

还为加密添加了生成的IV。

代码:

public class KeyHelper{


    private static final String RSA_MODE =  "RSA/ECB/PKCS1Padding";
    private static final String AES_MODE_M = "AES/GCM/NoPadding";

    private static final String KEY_ALIAS = "KEY";
    private static final String AndroidKeyStore = "AndroidKeyStore";
    public static final String SHARED_PREFENCE_NAME = "SAVED_TO_SHARED";
    public static final String ENCRYPTED_KEY = "ENCRYPTED_KEY";
    public static final String PUBLIC_IV = "PUBLIC_IV";


    private KeyStore keyStore;
    private static KeyHelper keyHelper;

    public static KeyHelper getInstance(Context ctx){
        if(keyHelper == null){
            try{
                keyHelper = new KeyHelper(ctx);
            } catch (NoSuchPaddingException | NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | KeyStoreException | CertificateException | IOException e){
                e.printStackTrace();
            }
        }
        return keyHelper;
    }

    public KeyHelper(Context ctx) throws  NoSuchPaddingException,NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException  {
        this.generateEncryptKey(ctx);
        this.generateRandomIV(ctx);
        if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M){
            try{
                this.generateAESKey(ctx);
            } catch(Exception e){
                e.printStackTrace();
            }
        }
    }


    private void generateEncryptKey(Context ctx) throws  NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException {

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

        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
            if (!keyStore.containsAlias(KEY_ALIAS)) {
                KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore);
                keyGenerator.init(
                        new KeyGenParameterSpec.Builder(KEY_ALIAS,
                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                                .setRandomizedEncryptionRequired(false)
                                .build());
                keyGenerator.generateKey();
            }
        } else{
            if (!keyStore.containsAlias(KEY_ALIAS)) {
                // Generate a key pair for encryption
                Calendar start = Calendar.getInstance();
                Calendar end = Calendar.getInstance();
                end.add(Calendar.YEAR, 30);
                KeyPairGeneratorSpec spec = new   KeyPairGeneratorSpec.Builder(ctx)
                        .setAlias(KEY_ALIAS)
                        .setSubject(new X500Principal("CN=" + KEY_ALIAS))
                        .setSerialNumber(BigInteger.TEN)
                        .setStartDate(start.getTime())
                        .setEndDate(end.getTime())
                        .build();
                KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, AndroidKeyStore);
                kpg.initialize(spec);
                kpg.generateKeyPair();
            }
        }


    }

    private byte[] rsaEncrypt(byte[] secret) throws Exception{
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null);
        // Encrypt the text
        Cipher inputCipher = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
        inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.getCertificate().getPublicKey());

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher);
        cipherOutputStream.write(secret);
        cipherOutputStream.close();

        return outputStream.toByteArray();
    }

    private  byte[]  rsaDecrypt(byte[] encrypted) throws Exception {
        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(KEY_ALIAS, null);
        Cipher output = Cipher.getInstance(RSA_MODE, "AndroidOpenSSL");
        output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
        CipherInputStream cipherInputStream = new CipherInputStream(
            new ByteArrayInputStream(encrypted), output);
        ArrayList<Byte> values = new ArrayList<>();
        int nextByte;
        while ((nextByte = cipherInputStream.read()) != -1) {
            values.add((byte)nextByte);
        }

        byte[] bytes = new byte[values.size()];
        for(int i = 0; i < bytes.length; i++) {
            bytes[i] = values.get(i).byteValue();
        }
        return bytes;
    }

    private void generateAESKey(Context context) throws  Exception{
        SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
        String enryptedKeyB64 = pref.getString(ENCRYPTED_KEY, null);
        if (enryptedKeyB64 == null) {
            byte[] key = new byte[16];
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(key);
            byte[] encryptedKey = rsaEncrypt(key);
            enryptedKeyB64 = Base64.encodeToString(encryptedKey, Base64.DEFAULT);
            SharedPreferences.Editor edit = pref.edit();
            edit.putString(ENCRYPTED_KEY, enryptedKeyB64);
            edit.apply();
        }
    }


    private Key getAESKeyFromKS() throws  NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException{
        keyStore = KeyStore.getInstance(AndroidKeyStore);
        keyStore.load(null);
        SecretKey key = (SecretKey)keyStore.getKey(KEY_ALIAS,null);
        return key;
    }


    private Key getSecretKey(Context context) throws Exception{
        SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
        String enryptedKeyB64 = pref.getString(ENCRYPTED_KEY, null);

        byte[] encryptedKey = Base64.decode(enryptedKeyB64, Base64.DEFAULT);
        byte[] key = rsaDecrypt(encryptedKey);
        return new SecretKeySpec(key, "AES");
    }

    public String encrypt(Context context, String input) throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        Cipher c;
        SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
        String publicIV = pref.getString(PUBLIC_IV, null);

        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
            c = Cipher.getInstance(AES_MODE_M);
            try{
                c.init(Cipher.ENCRYPT_MODE, getAESKeyFromKS(), new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
            } catch(Exception e){
                e.printStackTrace();
            }
        } else{
            c = Cipher.getInstance(AES_MODE_M);
            try{
                c.init(Cipher.ENCRYPT_MODE, getSecretKey(context),new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
            } catch (Exception e){
                e.printStackTrace();
            }
        }
        byte[] encodedBytes = c.doFinal(input.getBytes("UTF-8"));
        return Base64.encodeToString(encodedBytes, Base64.DEFAULT);
    }





    public String decrypt(Context context, String encrypted) throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException {
        Cipher c;
        SharedPreferences pref = context.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
        String publicIV = pref.getString(PUBLIC_IV, null);


        if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
            c = Cipher.getInstance(AES_MODE_M);
            try{
                c.init(Cipher.DECRYPT_MODE, getAESKeyFromKS(), new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));

            } catch(Exception e){
                e.printStackTrace();
            }
        } else{
            c = Cipher.getInstance(AES_MODE_M);
            try{
                c.init(Cipher.DECRYPT_MODE, getSecretKey(context), new GCMParameterSpec(128,Base64.decode(publicIV, Base64.DEFAULT)));
            } catch (Exception e){
                e.printStackTrace();
            }
        }

        byte[] decodedValue = Base64.decode(encrypted.getBytes("UTF-8"), Base64.DEFAULT);
        byte[] decryptedVal = c.doFinal(decodedValue);
        return new String(decryptedVal);
    }

    public void generateRandomIV(Context ctx){
        SharedPreferences pref = ctx.getSharedPreferences(SHARED_PREFENCE_NAME, Context.MODE_PRIVATE);
        String publicIV = pref.getString(PUBLIC_IV, null);

        if(publicIV == null){
            SecureRandom random = new SecureRandom();
            byte[] generated = random.generateSeed(12);
            String generatedIVstr = Base64.encodeToString(generated, Base64.DEFAULT);
            SharedPreferences.Editor edit = pref.edit();
            edit.putString(PUBLIC_IV_PERSONAL, generatedIVstr);
            edit.apply();
        }
    }

    private String getStringFromSharedPrefs(String key, Context ctx){
        SharedPreferences prefs = ctx.getSharedPreferences(MyConstants.APP_SHAREDPREFS, 0);
        return prefs.getString(key, null);
    }
}

注意:这仅适用于API 18及更高版本

答案 1 :(得分:1)

关于有关root设备安全性的问题,我建议您使用以下文件:

Analysis of Secure Key Storage Solutions on Android

答案 2 :(得分:0)

您可以加密smtp凭据并在应用程序空间中本地存储加密值(例如,在共享首选项中)。用于加密的密钥可以存储在密钥存储区中。

有关详细信息,请参阅:How Can I Use the Android KeyStore to securely store arbitrary strings?