在Android应用程序中存储凭据

我们如何安全地存储凭据数据以访问Android应用程序中的smtp服务器?这些数据是常量,只有开发人员才能知道.目前将它们存储在代码中,但这并不安全,因为可以通过反编译应用程序来查看它们.

是否可以为此目的使用Android Keystore System?如何使用?最重要的是,Android Keystore在植根设备上是否有效?

解决方法:

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

为了避免其他人看到纯文本格式的数据,一种解决方案是在存储数据之前对其进行加密. Android从API 18开始引入了KeyStore,它可以存储用于加密和解密数据的密钥.

在API 23之前的问题是您无法将AES密钥存储在KeyStore中,因此用于加密的最可靠的密钥是带有私钥和公钥的RSA.

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

对于23以下的API

>您将生成RSA私钥和公钥,并将其保存在KeyStore中,生成AES密钥,使用RSA公钥对其进行加密,然后将其保存到SharedPreferences中.
>每次需要使用AES密钥将加密的数据保存在SharedPreferences中时,都会从SharedPreferences获得加密的AES密钥,使用RSA私钥对其进行解密,并使用已经解密的AES密钥对要保存到SharedPreferences的数据进行加密.
>要解密数据,过程几乎相同,从SharedPreferences获取加密的AES密钥,使用RSA私钥解密,从要解密的SharedPreferences获取加密的数据,然后使用解密的AES密钥解密.

适用于API 23以上

>只需生成一个AES密钥并将其存储在KeyStore中,并在需要进行数据加密/解密时随时访问它.

还添加了用于加密的生成的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及更高版本

上一篇:如何在Python中将UTF-8加密成A-Z以外的形式?


下一篇:c-如何遍历河豚加密