Manipuler les données de façon plus sécurisée

  Inclus dans Android Jetpack.

La bibliothèque de sécurité implémente les bonnes pratiques de sécurité liées à la lecture et à l'écriture de données au repos, ainsi qu'à la création et à la vérification des clés.

La bibliothèque utilise le modèle de compilateur pour fournir des paramètres par défaut sécurisés pour les niveaux de sécurité suivants :

  • Une sécurité renforcée qui assure un juste équilibre entre un chiffrement élevé et de bonnes performances. Ce niveau de sécurité convient aux applications grand public telles que les applications bancaires et de chat, ainsi qu'aux applications d'entreprise qui vérifient la révocation des certificats.
  • Sécurité maximale. Ce niveau de sécurité convient aux applications qui nécessitent un keystore intégré au matériel et la présence de l'utilisateur pour fournir l'accès aux clés.

Ce guide explique comment utiliser les configurations de sécurité recommandées de la bibliothèque de sécurité, et comment lire et écrire des données chiffrées stockées dans des fichiers et des préférences partagées.

Gestion des clés

La bibliothèque de sécurité utilise un système en deux parties pour gérer les clés :

  • Un ensemble de clés contenant une ou plusieurs clés pour chiffrer un fichier ou des données de préférences partagées. L'ensemble de clés est stocké dans SharedPreferences.

  • Une clé (master) primaire qui chiffre tous les ensembles de clés. Cette clé est stockée à l'aide du système de keystore Android.

Classes incluses dans la bibliothèque

La bibliothèque de sécurité contient les classes suivantes pour fournir des données au repos plus sécurisées :

EncryptedFile

Fournit des implémentations personnalisées de FileInputStream et FileOutputStream, ce qui permet à votre application d'effectuer des opérations de lecture et d'écriture en flux continu plus sécurisées.

Pour fournir des opérations de lecture et d'écriture sécurisées à partir de flux de fichiers, la bibliothèque de sécurité utilise la primitive Streaming Authenticated Encryption with Associated Data (AEAD). Pour en savoir plus sur cette primitive, consultez la documentation de la bibliothèque Tink sur GitHub.

EncryptedSharedPreferences

Encapsule la classe SharedPreferences, et chiffre automatiquement les clés et les valeurs à l'aide d'une méthode à deux schémas :

  • Les clés sont chiffrées à l'aide d'un algorithme déterministe de sorte qu'elles puissent être chiffrées et correctement recherchées.
  • Les valeurs sont chiffrées à l'aide de la norme AES-256 GCM et ne sont pas déterministes.

Les sections suivantes expliquent comment utiliser ces classes pour effectuer des opérations courantes avec les fichiers et les préférences partagées.

Inclure la bibliothèque dans votre projet

Pour utiliser la bibliothèque de sécurité, ajoutez les dépendances suivantes, selon vos besoins, au fichier build.gradle de votre module d'application :

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

}

Lecture de fichiers

L'extrait de code suivant montre comment utiliser EncryptedFile pour lire le contenu d'un fichier :

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

Écrire des fichiers

L'extrait de code suivant montre comment utiliser EncryptedFile pour écrire le contenu d'un fichier :

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

Pour les cas d'utilisation nécessitant une sécurité supplémentaire, procédez comme suit :

  1. Créez un objet KeyGenParameterSpec.Builder, en transmettant true dans setUserAuthenticationRequired() et une valeur supérieure à 0 comme premier argument de setUserAuthenticationParameters().
  2. Invitez l'utilisateur à saisir des identifiants à l'aide de createConfirmDeviceCredentialIntent(). Découvrez comment demander l'authentification de l'utilisateur pour utiliser des clés.

  3. Ignorez onActivityResult() pour obtenir le rappel d'identifiant confirmé.

Modifier les préférences partagées

L'extrait de code suivant montre comment utiliser EncryptedSharedPreferences pour modifier les préférences partagées d'un utilisateur :

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

Prendre en charge les appareils Android 5.0 et Android 5.1

La version 1.1.0 de la bibliothèque de sécurité vous permet de prendre en charge les appareils exécutant Android 5.0 (niveau d'API 21) ou version ultérieure. Sur Android 5.0 et Android 5.1 (niveau d'API 22), vous ne pouvez pas utiliser le keystore Android pour stocker des ensembles de clés.

Lecture de fichiers

L'extrait de code suivant montre comment utiliser EncryptedFile pour lire le contenu d'un fichier utilisant la version 1.1.0 de la bibliothèque de sécurité :

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

Écrire des fichiers

L'extrait de code suivant montre comment utiliser EncryptedFile pour écrire le contenu d'un fichier utilisant la version 1.1.0 de la bibliothèque de sécurité :

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

Modifier les préférences partagées

L'extrait de code suivant montre comment utiliser EncryptedSharedPreferences pour modifier les préférences partagées d'un utilisateur utilisant la version 1.1.0 de la bibliothèque de sécurité :

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