Storing secrets in Android applications
What is the best place to keep your secrets secure on Android devices? This article shows available methods and our security recommendations.
TL;DR & Quick Recommendations
- Use public storage (/sdcard) only for files which are intended to be shared between different applications.
- Use a private application container for all internal files and data.
- Whenever possible, avoid storing secrets on the device.
- If you must store secrets on the device (i.e for biometric authorization), use the Android Keystore system to encrypt sensitive data.
- Keystore stores cryptographic keys and performs all cryptographic operations on dedicated hardware, separate from the operating system (TEE or SE).
- The most convenient ways to use Keystore are EncyptedSharedPreferences and EncyptedFile classes.
- Follow good mobile application development practices -> see our “Guidelines on mobile application security Android edition”.
Local data storage
Android applications need to store data on the device. Some of them are intended to work offline, thus data must be stored locally. The other kind of apps need the Internet connection, which entails the necessity of session handling, caching and enrollment. All of those require some data to be stored on the device. Fortunately, Android gives a couple options for storing the data.
Public storage is intended for files which need to be shared between different applications.
Any application with WRITE_EXTERNAL_STORAGE permission can save files to any path in /sdcard/ directory. Similarly, any app with READ_EXTERNAL_STORAGE can read any files in that directory. Typical use case of this storage type is any “Gallery” application. It needs access to all the photos stored on the device, and all of them by default are stored in /sdcard/DCIM folder.
Note that Android 10 introduced significant changes to public storage. Apps targeted for API 29 and higher will be granted only scoped access, for example to specific file types or only to app-specific directories: https://developer.android.com/training/data-storage#scoped-storage.
Application data which is intended only for internal use, should always be stored in the private application container. This container is located under following path :
Private containers have a linux-like user-based protection system. Android assigns a unique user ID for each application, and then this application is executed in a separate process owned by this user. This mechanism protects different applications/processes from accessing private data of another:
The bad news here is that any process with root privileges can read private data anyway:
Secrets and how to store them
Security sensitive applications, like mobile banking and password managers are processing private data. Such data should be protected, even from potential attackers with root access to the device. We know now that both of the above-mentioned storage systems do not ensure 100% security, so how should we store secrets?
First of all, we should avoid storing secrets locally at any cost. Whenever possible, we should process secrets only on the backend, and in most cases – it works!
However, there are some situations when we need to store secrets locally. One of them is biometric authorization. Application gets a response from Biometric API and then something must happen in order to establish a valid session. Usually PIN or any other authorization token is stored on the device and after successful biometric response, this secret is sent to the backend.
If an attacker can get access to the above-mentioned token, he can establish a valid session, thus taking over the victim’s account.
On Android, the solution is encryption of the secrets using Keystore, which is a dedicated store for cryptographic keys. Moreover, on modern devices Keystore is implemented on dedicated hardware (TEE or SE). This means that all keys and cryptographic operations are taking place on secure hardware, separate from the operating system, which cannot be accessed.
Note that in comparison to the Keychain on iOS, Keystore cannot directly hold secrets, so encrypted secrets must be stored in application private storage. There are several ways of using Keystore, but the most simple and efficient one is EncryptedSharedPreferences class, which implements standard SharePreferences. Developer needs to initiate encrypted preference file:
String secretKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC); String preferencesName = "secret_keeper"; Context context = getApplicationContext(); SharedPreferences sharedPreferences = EncryptedSharedPreferences.create( preferencesName, secretKey, context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM );
And that’s it! Then we can interact with the shared preference file in the usual way, forgetting that it is encrypted:
Adding new parameter:
String secret = sharedPreferences.getString("secret", null);
Above-mentioned method returns with plaintext value, despite that secret_keeper.xml file is encrypted:
Similarly, EncryptedFile class can be used to encrypt files and store them on the device.
Below links to Android documentation:
Developers should not consider mobile devices as trusted. Untrusted parties, which might be either users or hackers, have complete control of the device. We also recommend to consider the risk related to compromised devices (with unlocked root access) and avoid storing secrets directly on the device. If that is required, it should be stored in the application private container and encrypted with a key stored in the Keystore.
If you are currently in the process of developing an android application, be sure to implement the current best practices. For that, take a look at our Guidelines on mobile application security Android edition.
Feel free to reach me out. You can find me on Twitter on LinkedIn.
Head of Cloud Security