Search code examples
javaandroidencryptionandroid-keystore

Java, Android Keystore - supply unencrypted API key or secret for encrypting


As i am new to Android, i am working on hiding my API keys and found Android keystore the way to go. But when i see examples of how to use Android Keystore, one thing i am not understanding is how to supply the unencrypted original key for encryption? if i am storing in the code, wouldn't that beat the purpose of using Android Keystore?

from an article on storing secrets: https://medium.com/@ericfu/securely-storing-secrets-in-an-android-application-501f030ae5a3

  1. Generate a random key when the app runs the first time;
  2. When you want to store a secret, retrieve the key from KeyStore, encrypt the data with it, and then store the encrypted data in Preferences.
  3. When you want to read a secret, read the encrypted data from Preferences, get the key from KeyStore and then use the key to decrypt the data

In second point, it says encrypt the data with it. How to supply the data without exposing to the code/application? I apologize if this has been answered.

Thanks


Solution

  • private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
    private static final String AES_MODE = "AES/GCM/NoPadding";
    private static final String KEY_ALIAS = "MyNiceKey";
    

    Load the default AndroidKeyStore:

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

    Generate AES key inside the KeyStore which in the latest verision of android, it is hardware-backed keystore; it means that it is very hard to extract the bytes of the key from it:

    if (!keyStore.containsAlias(KEY_ALIAS)) {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
        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();
    }
    

    Anyway you should use .setRandomizedEncryptionRequired(true). There is no point to set up a faulty protocol. Otherwise, if you have to encrypt only few bytes(your API key) you could create an asymmetric public/private key and encrypt it with RSA so that you don't even need to provide the IV.

    Haing said that, when you get the secret key from the KeyStore:

     public static SecretKey getKeyStoreSecretKeyEntry(final String entryAlias)
                throws GeneralSecurityException, IOException {
            return ((KeyStore.SecretKeyEntry) getKeyStore().getEntry(entryAlias, null)).getSecretKey();
        }
    

    the returned SecretKey does not containt the Key Material (the real bytes of the key) but only its reference. So you can use it freely iside the Cipher to encrypt and decrypt what you want. In any case, you API key will be exposed enyway if you use it to make http request directly to you service. The best way to go in your case is to use a server like Google Firebase

    P.s. there is very a simple library from google that will save you time and headache: https://developer.android.com/jetpack/androidx/releases/security

    https://developer.android.com/topic/security/data

    Conclusion: The key you generate within the android Key Store is property of the user and it should be used to protect the user's private data. So it is not a good practice to encrypt an API Key, wich is the developer's private data, with the user key. Use a server to protect the API key.