(String: {%- set hs_blog_post_body -%} {%- set in_blog_post_body = true -%} <span id="hs_cos_wrapper_post_body" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_rich_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="rich_text"> <div class="blog-post__lead h2"> <h2>Introduction</h2> </div></span>)

Android Jetpack Security

Photo of Marcin Stramowski

Marcin Stramowski

Updated Jan 9, 2023 • 7 min read

Introduction

Security is undoubtedly an important element in mobile applications, but unfortunately, doing everything correctly is a complex task. Recently, Google released its security-crypto library as part of jetpack components to ease the process of making apps more secure.

At the moment, the library includes three main features:

  • MasterKeys
  • EncryptedSharedPreferences
  • EncryptedFile

AndroidX Security is using the Tink library under the hood. Tink is an open-source crypto library created by Google that provides cryptographic APIs.

Installation

To use this library, all you need to do is add this one line to the app’s Gradle file:

implementation `androidx.security:security-crypto:1.0.0-alpha02`

Security-crypto is currently in alpha, but you can monitor for new releases of it on the mvnrepository page. Its size is really small – after analyzing the compiled .apk file it added around 11.9 KB.

In order to use this library, you will need to set minSdkVersion to 23+ – because of the use of the new API, Keystore operations are especially stable ( KeyPairGeneratorSpec has been replaced by API 23 KeyGenParameterSpec).

MasterKeys

MasterKeys is a helper class that contains one public method getOrCreate which allows the developer to create a master key and then get an alias for it. Let’s analyze the code:

@NonNull
public static String getOrCreate(
        @NonNull KeyGenParameterSpec keyGenParameterSpec)
        throws GeneralSecurityException, IOException {
    validate(keyGenParameterSpec);
    if (!MasterKeys.keyExists(keyGenParameterSpec.getKeystoreAlias())) {
        generateKey(keyGenParameterSpec);
    }
    return keyGenParameterSpec.getKeystoreAlias();
}

The only parameter, the KeyGenParameterSpec specifies options like algorithm, block mode, padding, and key size. The default value placed under MasterKeys.AES256_GCM_SPEC creates the following object:


@NonNull
private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec(
        @NonNull String keyAlias) {
    KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
            keyAlias,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(KEY_SIZE);
    return builder.build();
}

Where keyAlias is _androidx_security_master_key_ and KEY_SIZE = 256.

EncryptedSharedPreferences

EncryptedSharedPreferences is a wrapper class of SharedPreferences that allows you to save and read the values that are encrypting and decrypting everything under the hood. Let’s see how to create an instance of it:

EncryptedSharedPreferences.create(
    PREFS_FILENAME,
    masterKeyAlias,
    applicationContext,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

The second parameter is an alias created using MasterKeys.getOrCreate(KeyGenParameterSpec) and the last two define the schemes used for encryption keys and values. At the moment, the only available EncryptedSharedPreferences.PrefKeyEncryptionScheme is AES256_SIV and the only available EncryptedSharedPreferences.PrefValueEncryptionScheme is AES256_GCM.

And then we are able to use it just like a typical SharedPreferences object. Here is an example of saving a String:

encryptedPrefs.edit {
    putString(ENC_KEY, value)
    apply()
}

And reading the saved value:

val string = encryptedPrefs.getString(ENC_KEY, null)

Here is a quick video showing how it works in my example:

android_security_prefs

EncryptedFile

EncryptedFile allows you to easily encrypt data using FileInputStream and decrypt using FileOutputStream. To create an instance of it, we need several things:

EncryptedFile.Builder(
    file,
    applicationContext,
    masterKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

The first parameter is a File object that defines the path and name of where encrypted data should be stored. In my example, I created an instance of it like this:

val file = File(filesDir, ENCRYPTED_FILE_NAME)

Using masterKeyAlias is exactly the same as in the EncryptedSharedPreferences example. The last parameter, fileEncryptionScheme defines the scheme of how the input/output stream should be encrypted or decrypted. At the moment, the only available value is AES256_GCM_HKDF_4KB. Below, I have listed some details about it:

  • Size of the main key: 32 bytes
  • HKDF algo: HMAC-SHA256
  • Size of AES-GCM derived keys: 32 bytes
  • Ciphertext segment size: 4096 bytes

For example purposes, I decided to download the README.md file from the example repository, accessible here using the OkHttp library, getting bytes from the response response.body!!.bytes() and then saving it by passing those bytes to following method:

private fun onFileDownloaded(bytes: ByteArray) {
    var encryptedOutputStream: FileOutputStream? = null
    try {
        encryptedOutputStream = encryptedFile.openFileOutput().apply { 
            write(bytes) 
        }
    } catch (e: Exception) {
        Log.e(„TAG”, „Could not open encrypted file”, e)
    } finally {
        encryptedOutputStream?.close()
    }
}

And then reading it:

private fun readFile(fileInput: () -> FileInputStream) {
    var fileInputStream: FileInputStream? = null
    try {
        fileInputStream = fileInput()
        val reader = BufferedReader(InputStreamReader(fileInputStream))
        val stringBuilder = StringBuilder()
        reader.forEachLine { line -> stringBuilder.appendln(line) }
        result.text = stringBuilder.toString()
    } catch (e: Exception) {
        Log.e(„TAG”, „Error occurred when reading file”, e)
    } finally {
        fileInputStream?.close()
    }
}

To encrypt and load the downloaded file content, I called:

readFile { encryptedFile.openFileInput() }

To load the file without encrypting to ensure that data is not readable without decrypting:

readFile { file.inputStream() }

Here is a video from the example showing how it works:

android_security_enc_file

Example project

The working code can be found in the following repository here.

Summary

AndroidX Security cleverly wraps complex security logic while exposing simple interfaces to developers. With this library and a few lines of code, it’s possible to make our application more secure and eliminate cases where the developer forgets about some crucial configuration. The only downside is that it forces the developer to make the application compatible with android Marshmallow and newer only, but keeping in mind the backstory about KeyGenParameterSpec and KeyPairGeneratorSpec, this decision is reasonable. Also, according to the distribution dashboard provided by Google, only a quarter of devices in the world use Android 5 or older and this number is drastically decreasing over time. We still need to remember that encrypting SharedPreferences and files in a project are one of many factors that make the app truly secure. Let's keep our fingers crossed for a stable release of this library soon.

Photo by Chris Panas on Unsplash

Photo of Marcin Stramowski

More posts by this author

Marcin Stramowski

Marcin graduated from Poznań University of Technology with a master's degree in Automation and...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: