Trabalhar com dados de forma mais segura

A biblioteca Security, parte do Android Jetpack, oferece uma implementação das práticas recomendadas de segurança relacionada à leitura e à gravação de dados em repouso, bem como à criação e à verificação de chaves.

A biblioteca usa o padrão de builder para fornecer configurações padrão seguras para os seguintes níveis de segurança:

  • Segurança forte que equilibra ótima criptografia e bom desempenho. Esse nível de segurança é adequado para apps de consumidor, como apps de chat e Internet banking, bem como apps empresariais que realizam verificação de revogação de certificados.
  • Segurança máxima. Esse nível de segurança é adequado para apps que exigem um keystore no hardware e presença de usuário para fornecer acesso a chaves.

Este guia mostra como trabalhar com as configurações de segurança recomendadas da biblioteca Security e como ler e gravar dados criptografados armazenados em arquivos e preferências compartilhadas com facilidade e segurança.

Gerenciamento de chaves

A biblioteca Security usa um sistema de duas partes para o gerenciamento de chaves:

  • Um conjunto de chaves que contém uma ou mais chaves para criptografar um arquivo ou dados de preferências compartilhados. O conjunto de chaves é armazenado em SharedPreferences.

  • Uma chave mestra que criptografa todos os conjuntos de chaves. Essa chave é armazenada por meio do sistema de keystores do Android.

Classes incluídas na biblioteca

A biblioteca Security contém as seguintes classes para fornecer dados mais seguros em repouso:

EncryptedFile

Oferece implementações personalizadas de FileInputStream e FileOutputStream, concedendo ao seu app operações de gravação e leitura de streaming mais seguras.

Para fornecer operações seguras de leitura e gravação a partir de fluxos de arquivos, a biblioteca Security usa o primitivo Criptografia autenticada de streaming com dados associados (AEAD, na sigla em inglês). Saiba mais sobre esse primitivo na documentação da biblioteca Tink (em inglês) no GitHub.

EncryptedSharedPreferences

Encapsula a classe SharedPreferences e criptografa automaticamente chaves e valores usando um método de dois esquemas:

  • Chaves são criptografadas por meio de um algoritmo de criptografia determinístico, para que possam ser criptografadas e pesquisadas corretamente.
  • Valores são criptografados por meio de GCM AES-256 (em inglês) e não são determinísticos.

As seções a seguir mostram como usar essas classes para realizar operações comuns com arquivos e preferências compartilhadas.

Ler arquivos

O snippet de código a seguir demonstra como usar EncryptedFile para ler o conteúdo de um arquivo de uma maneira mais segura:

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"
    lateinit var byteStream: ByteArrayOutputStream
    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";
    ByteArrayOutputStream byteStream;

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

Gravar arquivos

O snippet de código a seguir demonstra como usar EncryptedFile para gravar o conteúdo de um arquivo de uma maneira mais segura:

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.bufferedWriter().use { writer ->
        writer.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 FileWriter(encryptedFile));
        writer.write("MY SUPER-SECRET INFORMATION");
    } catch (GeneralSecurityException gse) {
        // Error occurred getting or creating keyset.
    } catch (IOException ex) {
        // Error occurred opening file for writing.
    }
    

Para os casos de uso que exigem mais segurança, siga estas etapas:

  1. Crie um objeto KeyGenParameterSpec.Builder transmitindo true para setUserAuthenticationRequired() e um valor maior que zero para setUserAuthenticationValidityDurationSeconds().
  2. Peça que o usuário insira as credenciais usando createConfirmDeviceCredentialIntent(). Saiba mais sobre como solicitar a autenticação do usuário para uso de chaves.

  3. Modifique onActivityResult() para receber o callback de credencial confirmado.

Para saber mais, consulte Exigir autenticação do usuário para o uso de chaves.

Editar preferências compartilhadas

O snippet de código a seguir demonstra como usar EncryptedSharedPreferences para editar o conjunto de preferências compartilhadas de um usuário de maneira mais segura:

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