Saving credentials in an Android app

How can we safely store credentials for accessing the smtp server in an Android application? These data are constants, and only the developer should know them. They are currently stored in code, but this is unsafe because they can be seen by decompiling the application.

Can I use the Android Keystore target system for this? And most importantly, will Android Keystore be effective on root devices?

+5
source share
3 answers

In Android applications, you can store data in SharedPreferences, but since this data is actually stored in a file, any user with access to the phone can access it. This means a security leak if you want to store credentials or any other sensitive data.

So that other people do not see this data in text form, the solution must encrypt the data before saving it. From API 18, Android introduced KeyStore, which can store keys in which you encrypt and decrypt data.

The problem is until API 23 stores AES keys in the KeyStore, so RSA with a private and public key was the most reliable encryption key.

So, the solution I came up with was:

For API below 23

  • You create the RSA private and public key and save it in the KeyStore, generate the AES key, encrypt it with the RSA public key and save it in SharedPreferences.
  • Each time you need to save encrypted data in SharedPreferences using the AES key, you get the AES encrypted key from SharedPreferences, decrypt it using the RSA secret key and encrypt the data you want to save in SharedPreferences with the already decrypted AES key.
  • To decrypt the data, the process is almost the same, get the encrypted AES key from SharedPreferences, decrypt it with the RSA private key, get the encrypted data from SharedPreferences that you want to decrypt, and decrypt it with the decrypted AES key.

For API 23 and above

  • just generates and saves the AES key in the KeyStore and accesses it whenever you want to encrypt / decrypt data.

A generated IV for encryption has also been added.

Code:

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); } } 

NOTE. This is only for API 18 and above.

+5
source

Regarding the security issue on the root device, I would recommend you the following article:

Analysis of Secure Key Storage solutions on Android

+1
source

You can encrypt smtp credentials and store encrypted values ​​locally in your application space (for example, in general settings). The key used for encryption can be stored in the keystore.

For more information, see How to use Android KeyStore to safely store arbitrary strings?

0
source

Source: https://habr.com/ru/post/1264953/


All Articles