The Android Developer Challenge is back! Submit your idea before December 2.

Work with data more securely

The Security library, part of Android Jetpack, 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 easily and safely.

Key management

The Security library uses a 2-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 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.

Read files

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

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 masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(directory, fileToRead),
    context,
    masterKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val contents = encryptedFile.bufferedReader().useLines { lines ->
    lines.fold("") { working, line ->
        "$working\n$line"
    }
}

Java

// 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 masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

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

StringBuffer stringBuffer = new StringBuffer();
try (BufferedReader reader =
             new BufferedReader(new FileReader(encryptedFile))) {

    String line = reader.readLine();
    while (line != null) {
        stringBuffer.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Error occurred opening raw file for reading.
} finally {
    String contents = stringBuffer.toString();
}

Write files

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

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 masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// 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 = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(directory, fileToWrite),
    context,
    masterKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

encryptedFile.openFileOutput().bufferedWriter().use {
    it.write("MY SUPER-SECRET INFORMATION")
}

Java

// 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 masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// 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.
String fileToWrite = "my_sensitive_data.txt";
try {
    EncryptedFile encryptedFile = new EncryptedFile.Builder(
            new File(directory, fileToWrite),
            context,
            masterKeyAlias,
            EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build();

    // Write to a file.
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
            encryptedFile.openFileOutput()));
    writer.write("MY SUPER-SECRET INFORMATION");
} catch (GeneralSecurityException gse) {
    // Error occurred getting or creating keyset.
} catch (IOException ex) {
    // Error occurred opening file for writing.
}

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 into setUserAuthenticationValidityDurationSeconds().
  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.

For more information, see Requiring user authentication for key use.

Edit shared preferences

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

Kotlin

val sharedPreferences = EncryptedSharedPreferences
    .create(
    fileName,
    masterKeyAlias,
    context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

val sharedPrefsEditor = sharedPreferences.edit()

Java

EncryptedSharedPreferences sharedPreferences = EncryptedSharedPreferences
        .create(
                fileName,
                masterKeyAlias,
                context,
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        );

SharedPreferences.Editor sharedPrefsEditor = sharedPreferences.edit();