Encryption on Android with Jetpack Security

We live in a mobile-first world. Mobile phones have become a big part of our lives. They store significant amounts of data. That data includes the private data that only you should know about. Social media account credentials, bank accounts, text messages, contacts, emails, and so on. Users expect you to protect their data from those that seek to access it without their knowing.

The smartphone is an untrusted device. The user may be running your app on a device that hasn’t the latest operating system version installed yet. That device may have some security vulnerabilities coming from the operating system side. You also can’t know who is installing your app. A person can install your app to gain unauthorized access to private data. We as developers need to use some sort of security measures to protect users against a wide range of threats. Most threats aim to take your private information stored on your phone. Your app’s credibility depends on how you manage your user’s data.

Security is often neglected in mobile app development.

Sensitive Data

There is no strict definition of what data is considered sensitive. That depends on your business and your application. You should analyze your app and understand what is important for your users. Then you can decide which data needs protection.

In the context of data protection, we have two types of data that an Android application should protect. Data at rest and data in transit. Data in transit is data that is actively moving. It can travel through the network, for example, when you’re transferring data from the device’s local storage to cloud storage. Data at rest is data that is not actively moving, such as data stored locally on your device. Most often, data at rest is a more valuable target than data in transit.

In this article, we’ll focus on protecting data at rest in the mobile devices.

There are many different approaches to protecting your local data. One way is to limit installation directories. Android allows you to install apps to external storage, such as SD cards. This is mostly due to the lower storage capacity of some devices. By doing that, it exposes security risk since anyone with access to the SD card has access to application data. To fix this, you can restrict your apps install location to internal storage. That way, your app can only be installed on the device. However, this solution works to some extent. The problem still exists on rooted devices.

Rooted Devices & Emulators

Root is the basic directory of Android. It is a directory where everything else is stored. Rooting is a process of allowing you to make changes to the root directory. This allows you to make changes to everything that’s under the Android system. Each application has its own directory for data. One application cannot access another application directory. When you root a device, you basically give access to anyone to read all the data and all of the directories.

Rooting a device introduces major security risks.

Emulators are also compromised devices. It is unlikely that a regular user will install your app on an emulator and try to hack it. Still, there are people who could do that. Security researchers whose job is to break stuff, game players who want to cheat in the games, malware authors who want to avoid detection of their malicious software or developers who want to exploit competitor’s apps. All of them could use an emulator to try to exploit your app. Some apps implement emulator detection to increase the difficulty of running the app on an emulated device. This impedes some tools and techniques reverse engineers like to use. This increased difficulty forces the reverse engineer to defeat the emulator checks or utilize the physical device. This limits the access required for large-scale device analysis.

One of the strategies for keeping your application’s data secure is to encrypt your storage.

Encryption

Encryption plays a major role in data protection and is a popular tool for securing your data. It is a process of taking normal information, like text and turning it into a version that can’t be read by other people. To do that conversion, you need a key. Once you have a key, you can unlock or decrypt that information. Without the key, it remains in an unreadable format, or locked.

There is a whole field of study called cryptography. Cryptography focuses on securing and protecting data. It prevents an unauthorized person from accessing confidential data. Encryption is one part of the functionality of cryptography.

It’s useful to know how to implement security by yourself properly. There are powerful Android APIs focusing on data encryption. However, having your own implementation requires you to know the ins and outs of cryptography. If you don’t implement it properly, it can lead to mistakes. An alternative is using an industry-approved or time-tested third-party library. There’s one drawback of using a library. If hackers expose a vulnerability in a library, it will affect all apps that rely on that library. Having a custom implementation, your app will be immune to those kinds of attacks.

Once you encrypt the data, you have to decrypt it on-the-fly every time you access it. Therefore, you may see a bit of a performance drop once you enable encryption. It’s generally not noticeable for most users, especially if on a powerful phone.

Recently, Google came up with the Jetpack Security crypto library.

Jetpack Security

When you try to search the web to learn how to encrypt data on Android, you’ll find very little information. Examples you’ll find are most often out of date, or in many cases, incorrect. At the Google IO 2019, Google announced the new Jetpack Security library, which aims to solve the issues mentioned earlier. Jetpack Security provides an abstraction over security. It is a wrapper around primitives that are built into the platform. It makes encrypting files and shared preferences more straightforward.

There are three main things that Jetpack Security does for you:

  • Key management

Key Management

If you want to keep something secret, you need to limit the access to the encryption key. The encryption key is used for encryption and decryption of data. In real life, you always carry your car keys with you to make sure you don’t lose them or get them stolen. Jetpack Security uses something called Android Keystore.

The Android Keystore system lets you store cryptographic keys in a container to make it more difficult to extract from the device. The Android Keystore provides access to specialized secure hardware for storing cryptographic keys. Even though your app has access to the keys, it doesn’t know what the key content is. Keys are generated within the secure hardware and then used to perform cryptographic operations on user data, without the keys ever leaving the secure hardware. It is not possible to extract the keys stored in the secure hardware in an easy way. Your key will be safe, even on a rooted device.

With Jetpack Security, you can easily store your key to the Android Keystore using the MasterKeys class. This class allows you to use keystore keys out of the box. MasterKeys class uses a well-known recommended standard, Advanced Encryption Standard (AES). It's basically one of the most secure things that you can use on Android.

All keys in Android Keystore have an alias. Keystore is kind of like a map where you have a description of a key for each key you have. Jetpack Security has two types of keys, master key, and subkeys. Subkeys are used for encryption and decryption of data. The master key encrypts all subkeys. Subkeys are encrypted to add an extra security layer. Only the master key is stored in the Android Keystore to make it more difficult to extract from the device. Subkeys are encrypted and stored in the shared preferences.

MasterKeys class allows you to get a recommended default master key with default settings. This is how you can get the default master key:

val keyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

MasterKeys class contains methods to create and obtain master keys in Android Keystore. getOrCreate method gets or creates the master key provided. The encryption scheme is a required field, and for now, there is only one value that you can use, MasterKeys.AES256_GCM_SPEC.

If you’d rather have your own configuration, you can create your own custom master key using the KeyGenParameterSpec class. You can also add an extra level of protection by using a biometric prompt. Fingerprint or face identification. That would require authorization for both encryption and decryption of keys.

File Encryption

When you have a master key, you’re all set. You can start encrypting. Jetpack Security has a file encryption feature that enables you to create a file as you would before, except now it is also encrypted at the same time. This is how you can encrypt a file with Jetpack Security library:

val encryptedFile = EncryptedFile.Builder( 
File(cacheDir, "my_encrypted_file.txt"),
context,
MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

EncryptedFile class provides custom implementations of FileInputStream and FileOutputStream. It grants your app more secure read and write operations. EncryptedFile is a streaming file so you can encrypt very large files with this.

MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC) returns the default master key with default settings. EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB is an encryption scheme used to encrypt files. Currently, this is the only file encryption scheme that you can use.

When you have an encrypted file, you can write to it using openFileOutput method. Also, you can read from it using openFileInput method:

encryptedFile.openFileOutput().bufferedWriter().use { bufferedWriter -> // Write data 
}
encryptedFile.openFileInput().bufferedReader().useLines { lines -> // Read data
}

This works exactly the way you would normally expect it to, and everything is encrypted and decrypted under the hood.

SharedPreferences Encryption

In addition to encrypting files, you can also encrypt shared preferences with Jetpack Security. Encrypting shared preferences is similar to encrypting files. You can encrypt shared preferences like this:

val sharedPrefs = EncryptedSharedPreferences.create(         "my_secure_preferences",  MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC), context, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM 
)

You create an EncryptedSharedPreferences object. EncryptedSharedPreferences class wraps the SharedPreferences class and automatically encrypts keys and values. Again, you use the default master key. The important thing to mention is that the keys and values are encrypted differently. AES256_SIV is an encryption scheme used to encrypt keys. This scheme means that every time you encrypt a key, it's going to be encrypted the same way. This allows the keys to be looked up. Keys are encrypted because sensitive information can also be put as the key. Values are encrypted in a non- deterministic way using AES-256 GCM scheme. Again, there is only a single encryption scheme for keys and values, respectively.

You use EncryptedSharedPreferences object you created like you would use regular SharedPreferences object. For example:

sharedPrefs.edit().putString("my_key", "my_value").apply()

Reading and writing operations on the encrypted shared preferences are a bit slower since they are decrypted every time you access it. There is a significant performance difference when initializing encrypted shared preferences. If you’re initializing encrypted shared preferences on application launch, it can prolong your startup time and degrade the user’s experience. Make sure you only encrypt what is necessary.

Wrap-up

Jetpack Security wraps complex security logic while exposing simple API to developers. It is easy to configure and use, and it allows you to make your application more secure with just a few lines of code.

The downside of using this library is that it’s only compatible with devices running Android 6.0 (API 23) and newer. There’s still around 25% of Android devices running Android 5 or older. Jetpack Security also doesn’t support database encryption. If you need to encrypt a database, you can use some other third-party library, like SQLCipher.

It’s important to mention that the Jetpack Security library is still in beta, so we hope to see a stable release soon.

Originally published at https://five.agency on April 27, 2020.

Five is a mobile design and development agency with offices in Croatia and NYC.

Five is a mobile design and development agency with offices in Croatia and NYC.