When storing local data in a mobile application, it is common to use a database that can be encrypted. When encryption of this database is enabled,
the encryption key must be protected properly.
Why is this an issue?
Mobile applications often need to store data (which might be sensitive) locally. For Android, there exist several libraries that simplify this
process by offering a feature-rich database system. SQLCipher and Realm are examples of such libraries. These libraries often add support for database encryption, to protect the contents
from being read by other apps or by attackers.
When using encryption for such a database, it is important that the encryption key stays secret. If this key is hardcoded in the application, then
it should be considered compromised. The key will be known by anyone with access to the application’s binary code or source code. This means that the
sensitive encrypted data can be decrypted by anyone having access to the binary of the mobile application.
Furthermore, if the key is hardcoded, it is the same for every user. A compromise of this encryption key implicates every user of the app.
The encryption key is meant to stay secret and should not be hard-coded in the application as it would mean that:
What is the potential impact?
If an attacker is able to find the encryption key for the mobile database, this can potentially have severe consequences.
Theft of sensitive data
If a mobile database is encrypted, it is likely to contain data that is sensitive for the user or the app publisher. For example, it can contain
personally identifiable information (PII), financial data, login credentials, or other sensitive user data.
By not protecting the encryption key properly, it becomes very easy for an attacker to recover it and then decrypt the mobile database. At that
point, the theft of sensitive data might lead to identity theft, financial fraud, and other forms of malicious activities.
How to fix it in Realm
Code examples
In the example below, a local database is opened using a hardcoded key. To fix this, the key is moved to a secure location instead and retrieved
using a getKey()
method.
Noncompliant code example
String key = "gb09ym9ydoolp3w886d0tciczj6ve9kszqd65u7d126040gwy86xqimjpuuc788g";
RealmConfiguration config = new RealmConfiguration.Builder();
.encryptionKey(key.toByteArray()) // Noncompliant
.build();
Realm realm = Realm.getInstance(config);
Compliant solution
RealmConfiguration config = new RealmConfiguration.Builder()
.encryptionKey(getKey())
.build();
Realm realm = Realm.getInstance(config);
How does this work?
Using Android’s builtin key storage options
The Android Keystore system allows apps to store encryption keys in a
container that is protected on a system level. Additionally, it can restrict when and how the keys are used. For example, it allows the app to require
user authentication (for example using a fingerprint) before the key is made available. This is the recommended way to store cryptographic keys on
Android.
Dynamically retrieving encryption keys remotely
As user devices are less trusted than controlled environments such as the application backend, the latter should be preferred for the storage of
encryption keys. This requires that a user’s device has an internet connection, which may not be suitable for every use case.
Going the extra mile
Avoid storing sensitive data on user devices
In general, it is always preferable to store as little sensitive data on user devices as possible.
Of course, some sensitive data always has to be stored on client devices, such as the data required for authentication. In this case, consider
whether the application logic can also function with a hash (or otherwise non-reversible form) of that data. For example, if an email address is
required for authentication, it might be possible to use and store a hashed version of this address instead.
How to fix it in SQLCipher
Code examples
In the example below, a local database is opened using a hardcoded key. To fix this, the key is moved to a secure location instead and retrieved
using a getKey()
method.
Noncompliant code example
String key = "gb09ym9ydoolp3w886d0tciczj6ve9kszqd65u7d126040gwy86xqimjpuuc788g";
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase("test.db", key, null); // Noncompliant
Compliant solution
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase("test.db", getKey(), null);
How does this work?
Using Android’s builtin key storage options
The Android Keystore system allows apps to store encryption keys in a
container that is protected on a system level. Additionally, it can restrict when and how the keys are used. For example, it allows the app to require
user authentication (for example using a fingerprint) before the key is made available. This is the recommended way to store cryptographic keys on
Android.
Dynamically retrieving encryption keys remotely
As user devices are less trusted than controlled environments such as the application backend, the latter should be preferred for the storage of
encryption keys. This requires that a user’s device has an internet connection, which may not be suitable for every use case.
Going the extra mile
Avoid storing sensitive data on user devices
In general, it is always preferable to store as little sensitive data on user devices as possible.
Of course, some sensitive data always has to be stored on client devices, such as the data required for authentication. In this case, consider
whether the application logic can also function with a hash (or otherwise non-reversible form) of that data. For example, if an email address is
required for authentication, it might be possible to use and store a hashed version of this address instead.
Resources
Documentation
Standards