Cómo trabajar con datos de forma más segura

Parte de Android Jetpack.

La biblioteca de seguridad proporciona una implementación de las prácticas recomendadas de seguridad en relación con la lectura y la escritura de datos en reposo, y la creación y la verificación de claves.

La biblioteca utiliza el patrón de compilador a fin de proporcionar configuraciones predeterminadas seguras para los siguientes niveles de seguridad:

  • Seguridad sólida que equilibra una excelente encriptación y un buen rendimiento. Este nivel de seguridad es apropiado para aplicaciones para consumidores, como apps bancarias y de chat, y para apps empresariales que realizan comprobaciones de revocación de certificados.
  • Seguridad máxima. Este nivel de seguridad es adecuado para las apps que requieren un almacén de claves con copia de seguridad en hardware y la presencia del usuario para proporcionar acceso a las claves.

En esta guía, se muestra cómo trabajar con las configuraciones de seguridad recomendadas de la biblioteca de seguridad y se incluye información sobre cómo leer y escribir datos encriptados que se almacenan en archivos y preferencias compartidas.

Administración de claves

La biblioteca de seguridad utiliza un sistema de dos partes para la administración de claves:

  • Un conjunto de claves que contiene una o más claves para encriptar un archivo o datos de preferencias compartidas. Se almacena el conjunto de claves en SharedPreferences.

  • Una clave principal (master) que encripta todos los conjuntos de claves. Esta se almacena utilizando el sistema del almacén de claves de Android.

Clases incluidas en la biblioteca

La biblioteca de seguridad incluye las siguientes clases a fin de proporcionar datos más seguros en reposo:

EncryptedFile

Proporciona implementaciones personalizadas de FileInputStream y FileOutputStream, lo que otorga a tu app operaciones de transmisión de lectura y escritura más seguras.

Para proporcionar operaciones seguras de lectura y escritura desde transmisiones de archivos, la biblioteca de seguridad utiliza el tipo primitivo de encriptación autenticada con datos asociados (AEAD). Obtén más información sobre este tipo primitivo en la documentación de la biblioteca Tink, en GitHub.

EncryptedSharedPreferences

Envuelve la clase SharedPreferences y encripta automáticamente claves y valores utilizando un método de dos esquemas:

  • Se encriptan las claves mediante un algoritmo de encriptación determinista, de modo que sea posible encriptar la clave y buscarla correctamente.
  • Se encriptan los valores con AES-256 GCM de una manera no determinista.

En las siguientes secciones, se muestra cómo usar estas clases para realizar operaciones comunes con archivos y preferencias compartidas.

Cómo incluir la biblioteca en tu proyecto

A fin de usar la biblioteca de seguridad, agrega las siguientes dependencias, según corresponda, al archivo build.gradle del módulo de tu app:

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")

}

Cómo leer archivos

En el siguiente fragmento de código, se muestra cómo usar EncryptedFile para leer el contenido de un archivo:

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();

Cómo escribir archivos

En el siguiente fragmento de código, se muestra cómo usar EncryptedFile para escribir el contenido de un archivo:

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();

En los casos prácticos que requieran seguridad adicional, completa los siguientes pasos:

  1. Crea un objeto KeyGenParameterSpec.Builder y pasa true a setUserAuthenticationRequired() y un valor superior a 0 como primer argumento de setUserAuthenticationParameters().
  2. Solicita al usuario que ingrese las credenciales mediante createConfirmDeviceCredentialIntent(). Obtén más información sobre cómo solicitar la autenticación de usuarios para el uso de la clave.

  3. Anula onActivityResult() para obtener la devolución de llamada de la credencial confirmada.

Edita preferencias compartidas

En el siguiente fragmento de código, se muestra cómo usar EncryptedSharedPreferences para editar el conjunto de preferencias compartidas de un usuario:

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();

Compatible con dispositivos Android 5.0 y Android 5.1

La versión 1.1.0 de la biblioteca de seguridad te permite admitir dispositivos que ejecutan Android 5.0 (nivel de API 21) y versiones posteriores. En Android 5.0 y Android 5.1 (nivel de API 22), no puedes usar el almacén de claves de Android para almacenar conjuntos de claves.

Cómo leer archivos

En el siguiente fragmento de código, se muestra cómo usar EncryptedFile para leer el contenido de un archivo con la versión 1.1.0 de la biblioteca de seguridad:

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();

Cómo escribir archivos

En el siguiente fragmento de código, se muestra cómo usar EncryptedFile para escribir el contenido de un archivo con la versión 1.1.0 de la biblioteca de seguridad:

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();

Cómo editar preferencias compartidas

En el siguiente fragmento de código, se muestra cómo usar EncryptedSharedPreferences para editar el conjunto de preferencias compartidas de un usuario con la versión 1.1.0 de la biblioteca de seguridad:

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();