How to securely store access token and secret in Android?

AndroidSecurityOauthPreferencesToken

Android Problem Overview


I am going to use oAuth to fetch mails and contacts from google. I don't want to ask the user each time to log in to obtain an access token and secret. From what I understood, I need to store them with my application either in a database or SharedPreferences. But I am a bit worried about security aspects with that. I read that you can encrypt and decrypt the tokens but it is easy for an attacker to just decompile your apk and classes and get the encryption key.
What's the best method to securely store these tokens in Android?

Android Solutions


Solution 1 - Android

Store them as shared preferences. Those are by default private, and other apps cannot access them. On a rooted devices, if the user explicitly allows access to some app that is trying to read them, the app might be able to use them, but you cannot protect against that. As for encryption, you have to either require the user to enter the decrypt passphrase every time (thus defeating the purpose of caching credentials), or save the key to a file, and you get the same problem.

There are a few benefits of storing tokens instead of the actual username password:

  • Third party apps don't need to know the password and the user can be sure that they only send it to the original site (Facebook, Twitter, Gmail, etc.)
  • Even if someone steals a token, they don't get to see the password (which the user might be using on other sites too)
  • Tokens generally have a lifetime and expire after a certain time
  • Tokens can be revoked if you suspect they have been compromised

Solution 2 - Android

You can store them in AccountManager. It's considered best practice according to these guys.

enter image description here

Here's the official definition:

> This class provides access to a centralized registry of the user's > online accounts. The user enters credentials (username and password) > once per account, granting applications access to online resources > with "one-click" approval.

For detailed guide on how to use AccountManager:

However, in the end AccountManager only stores your token as a plain text. So, I would suggest encrypting your secret before storing them in AccountManager. You can utilize various Encryption library like AESCrypt or AESCrypto

Another option is to use Conceal library. It's safe enough for Facebook and much easier to use than AccountManager. Here's a code snippet to save a secret file using Conceal.

byte[] cipherText = crypto.encrypt(plainText);
byte[] plainText = crypto.decrypt(cipherText);

Solution 3 - Android

SharedPreferences is not a secure location itself. On a rooted device we easily can read and modify all applications' SharedPrefereces xml's. So tokens should expire relatively frequent. But even if a token expires every hour, newer tokens can still be stolen from SharedPreferences. Android KeyStore should be used for long term storage and retrieval of cryptographic keys which will be used to encrypt our tokens in order to store them in e.g. SharedPreferences or a database. The keys are not stored within an application's process, so they are harder to be compromised.

So more relevant than a place is how they can be itself secure e.g. using cryptographically signed short-living JWTs, encrypting them using Android KeyStore and sending them with a secure protocol

Solution 4 - Android

  1. From your Android Studio's Project pane, select "Project Files" and create a new file named "keystore.properties" in your project's root directory.

enter image description here

  1. Open "keystore.properties" file and save your Access Token and Secret in the file.

enter image description here

  1. Now load the read the Access Token and Secret in your app module's build.gradle file. Then you need to define the BuildConfig variable for your Access Token and Secret so that you can you can directly access them from your code. Your build.gradle may look like following:

    ... ... ... 
    
    android {
        compileSdkVersion 26
    
        // Load values from keystore.properties file
        def keystorePropertiesFile = rootProject.file("keystore.properties")
        def keystoreProperties = new Properties()
        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
    
        defaultConfig {
            applicationId "com.yourdomain.appname"
            minSdkVersion 16
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            
            // Create BuildConfig variables
            buildConfigField "String", "ACCESS_TOKEN", keystoreProperties["ACCESS_TOKEN"]
            buildConfigField "String", "SECRET", keystoreProperties["SECRET"]
        }
    }
    
  2. You can use your Access Token and Secret in your code like this:

    String accessToken = BuildConfig.ACCESS_TOKEN;
    String secret = BuildConfig.SECRET;
    

This way you don't need store the Access Token and Secret in plain text inside your project. So even if someone decompiles your APK, they will never get your Access Token and Secret as you are loading them from a external file.

Solution 5 - Android

Just as a late update to this question, you can now use EncryptedSharedPreferences to store data securely. The interface is very similar except that you also need to generate a MasterKey.

Most of the docs for EncryptedSharedPreferences use MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC, but that appears to be deprecated in favor of MasterKey.Builder.

private var masterKeyAlias = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build() 

private val preferences = EncryptedSharedPreferences.create(
            context,
            "auth_token_secured",
            masterKeyAlias,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )

var authToken: String?
    get() = preferences.getString("auth_token", "")
    set(value) = preferences.edit().putString("auth_token", value).apply()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionyeahmanView Question on Stackoverflow
Solution 1 - AndroidNikolay ElenkovView Answer on Stackoverflow
Solution 2 - AndroidaldokView Answer on Stackoverflow
Solution 3 - Androidapex39View Answer on Stackoverflow
Solution 4 - AndroidZahidur Rahman FaisalView Answer on Stackoverflow
Solution 5 - AndroidkfryeView Answer on Stackoverflow