Work with data more securely

  Part of Android Jetpack.

The Security library provides an implementation of the security best practices related to reading and writing data at rest, as well as key creation and verification.

The library uses the builder pattern to provide safe default settings for the following security levels:

  • Strong security that balances great encryption and good performance. This level of security is appropriate for consumer apps, such as banking and chat apps, as well as enterprise apps that perform certificate revocation checking.
  • Maximum security. This level of security is appropriate for apps that require a hardware-backed keystore and user presence for providing key access.

This guide shows how to work with the Security library's recommended security configurations as well as how to read and write encrypted data that's stored in files and shared preferences.

Key management

The Security library uses a two-part system for key management:

  • A keyset that contains one or more keys to encrypt a file or shared preferences data. The keyset itself is stored in SharedPreferences.

  • A primary (master) key that encrypts all keysets. This key is stored using the Android keystore system.

Classes included in library

The Security library contains the following classes to provide more secure data at rest:

EncryptedFile

Provides custom implementations of FileInputStream and FileOutputStream, granting your app more secure streaming read and write operations.

To provide secure read and write operations from file streams, the Security library uses the Streaming Authenticated Encryption with Associated Data (AEAD) primitive. Learn more about this primitive in the Tink library documentation on GitHub.

EncryptedSharedPreferences

Wraps the SharedPreferences class and automatically encrypts keys and values using a two-scheme method:

  • Keys are encrypted using a deterministic encryption algorithm such that the key can be encrypted and properly looked up.
  • Values are encrypted using AES-256 GCM and are non-deterministic.

The following sections show how to use these classes to perform common operations with files and shared preferences.

Include the library in your project

To use the Security library, add the following dependencies, as appropriate, to your app module's build.gradle file:

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.0.0"

    // For Identity Credential APIs
    implementation "androidx.security:security-identity-credential:1.0.0-alpha03"

     // For App Authentication APIs
    implementation "androidx.security:security-app-authenticator:1.0.0-alpha02"

    // For App Authentication API testing
    androidTestImplementation "androidx.security:security-app-authenticator:1.0.0-alpha01"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.0.0")

    // For Identity Credential APIs
    implementation("androidx.security:security-identity-credential:1.0.0-alpha03")

    // For App Authentication APIs
    implementation("androidx.security:security-app-authenticator:1.0.0-alpha02")

    // For App Authentication API testing
    androidTestImplementation("androidx.security:security-app-authenticator:1.0.0-alpha01")

}

Read files

The following code snippet demonstrates how to use EncryptedFile to read the contents of a file:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToRead),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

Write files

The following code snippet demonstrates how to use EncryptedFile to write the contents of a file:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

Java

Context context = getApplicationContext();

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
String mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
String fileToWrite = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToWrite),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

For use cases requiring additional security, complete the following steps:

  1. Create a KeyGenParameterSpec.Builder object, passing true into setUserAuthenticationRequired() and a value greater than 0 as the first argument of setUserAuthenticationParameters().
  2. Prompt the user to enter credentials using createConfirmDeviceCredentialIntent(). Learn more about how to request user authentication for key use.

  3. Override onActivityResult() to get the confirmed credential callback.

Edit shared preferences

The following code snippet demonstrates how to use EncryptedSharedPreferences to edit a user's set of shared preferences:

Kotlin

val sharedPrefsFile: String = FILE_NAME
val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
        sharedPrefsFile,
        mainKeyAlias,
        applicationContext,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

with (sharedPreferences.edit()) {
    // Edit the user's shared preferences...
    apply()
}

Java

String sharedPrefsFile = FILE_NAME;
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
        sharedPrefsFile,
        mainKeyAlias,
        getApplicationContext(),
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
// Edit the user's shared preferences...
sharedPrefsEditor.apply();

Support Android 5.0 and Android 5.1 devices

Version 1.1.0 of the Security library lets you support devices that run Android 5.0 (API level 21) and higher. On Android 5.0 and Android 5.1 (API level 22), you can't use the Android keystore to store keysets.

Read files

The following code snippet demonstrates how to use EncryptedFile to read the contents of a file using version 1.1.0 of the Security library:

Kotlin

val mainKey = MasterKey.Builder(applicationContext)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(applicationContext,
        File(DIRECTORY, fileToRead),
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

Java

Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

String fileToRead = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(context,
        new File(DIRECTORY, fileToRead),
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

Write files

The following code snippet demonstrates how to use EncryptedFile to write the contents of a file using version 1.1.0 of the Security library:

Kotlin

val mainKey = MasterKey.Builder(applicationContext)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

// Creates a file with this name, or replaces an existing file
// that has the same name. Note that the file name cannot contain
// path separators.
val fileToWrite = File(DIRECTORY, "my_sensitive_data.txt")
val encryptedFile = EncryptedFile.Builder(applicationContext,
        fileToWrite,
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

// File cannot exist before using openFileOutput
if (fileToWrite.exists()) {
    fileToWrite.delete()
}

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8))
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

Java

Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

// Creates a file with this name, or replaces an existing file
// that has the same name. Note that the file name cannot contain
// path separators.
File fileToWrite = new File(DIRECTORY, "my_sensitive_data.txt");
EncryptedFile encryptedFile = new EncryptedFile.Builder(context,
        fileToWrite,
        mainKey,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

// File cannot exist before using openFileOutput
if (fileToWrite.exists()) {
    fileToWrite.delete();
}

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

Edit shared preferences

The following code snippet demonstrates how to use EncryptedSharedPreferences to edit a user's set of shared preferences using version 1.1.0 of the Security library:

Kotlin

val context = applicationContext
val mainKey = MasterKey.Builder(applicationContext)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

val sharedPreferences = EncryptedSharedPreferences.create(
    applicationContext,
    FILE_NAME,
    mainKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

with (sharedPreferences.edit()) {
    // Edit the user's shared preferences...
    apply()
}

Java

Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

SharedPreferences sharedPreferences = EncryptedSharedPreferences
        .create(
            context,
            FILE_NAME,
            mainKey,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        );

SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();
// Edit the user's shared preferences...
sharedPrefsEditor.apply();