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.

    The following code snippet demonstrates how to define a master key:

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

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:

val fileToRead = "my_sensitive_data.txt"
lateinit var byteStream: ByteArrayOutputStream
val encryptedFile = EncryptedFile.Builder(
    File(context.getFilesDir(), fileToRead),
    context,
    masterKeyAlias,
    EncryptedFileKeyset.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

try {
    encryptedFile.openFileOutput({ }, { fileInputStream ->
        try {
            byteStream = ByteArrayOutputStream()
            nextByte = fileInputStream.read()
            while (nextByte != -1) {
                byteStream.write(nextByte)
                nextByte = fileInputStream.read()
            }

            val fileContents = byteStream.toByteArray()

        } catch (ex: Exception) {
            // Error occurred opening raw file for reading.
        } finally {
            fileInputStream.close()
        }
    })
} catch (ex: IOException) {
    // Error occurred opening encrypted file for reading.
}

Write files

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

val fileToWrite = "my_other_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(context.getFilesDir(), fileToWrite),
    context,
    masterKeyAlias,
    EncryptedFileKeyset.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

// Write to a file.
try {
    val outputStream: FileOutputStream? = encryptedFile.openFileOutput()
    outputStream?.apply {
        write("MY SUPER SECRET INFORMATION"
            .toByteArray(Charset.forName("UTF-8")))
        flush()
        close()
    }
} catch (ex: IOException) {
    // 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:

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

val sharedPrefsEditor = sharedPreferences.edit()