Melhorar a segurança do seu app

Ao deixar seu app mais seguro, você ajuda a preservar a confiança do usuário e a integridade do dispositivo.

Está página apresenta diversas práticas recomendadas que têm um impacto positivo e significativo na segurança do app.

Aplicar comunicação segura

Quando você protege os dados trocados entre seu app e outros apps ou entre seu app e um site, você melhora a estabilidade do app e protege os dados enviados e recebidos.

Proteger a comunicação entre apps

Para se comunicar entre apps de forma mais segura, use intents implícitas com um seletor de apps, permissões baseadas em assinatura e provedores de conteúdo não exportados.

Mostrar um seletor de app

Se uma intent implícita puder iniciar pelo menos dois apps no dispositivo de um usuário, mostre um seletor de app de maneira explícita. Essa estratégia de interação permite que os usuários transfiram informações sensíveis para um app de confiança.

Kotlin

val intent = Intent(Intent.ACTION_SEND)
val possibleActivitiesList: List<ResolveInfo> =
        packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL)

// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with."

    val chooser = resources.getString(R.string.chooser_title).let { title ->
        Intent.createChooser(intent, title)
    }
    startActivity(chooser)
} else if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

Java

Intent intent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> possibleActivitiesList = getPackageManager()
        .queryIntentActivities(intent, PackageManager.MATCH_ALL);

// Verify that an activity in at least two apps on the user's device
// can handle the intent. Otherwise, start the intent only if an app
// on the user's device can handle the intent.
if (possibleActivitiesList.size() > 1) {

    // Create intent to show chooser.
    // Title is something similar to "Share this photo with."

    String title = getResources().getString(R.string.chooser_title);
    Intent chooser = Intent.createChooser(intent, title);
    startActivity(chooser);
} else if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
}

Informações relacionadas:

Aplicar permissões baseadas em assinatura

Ao compartilhar dados entre dois apps que você controla ou possui, use permissões baseadas em assinatura. Essas permissões não exigem confirmação do usuário. Em vez disso, elas verificam se os apps que acessam os dados são assinados com a mesma chave de assinatura. Portanto, essas permissões oferecem uma experiência do usuário mais simples e segura.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <permission android:name="my_custom_permission_name"
                android:protectionLevel="signature" />

Informações relacionadas:

Cancelar permissão de acesso aos provedores de conteúdo do seu app

A menos que você pretenda enviar dados do app para outro que não seja seu, impeça de maneira explícita que os apps de outros desenvolvedores acessem os objetos ContentProvider do seu app. Essa configuração é importante especialmente caso seu app possa ser instalado em dispositivos com o Android 4.1.1 (nível 16 da API) ou anterior, uma vez que o atributo android:exported do elemento <provider> é true por padrão nessas versões do Android.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application ... >
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            ...
            android:exported="false">
            <!-- Place child elements of <provider> here. -->
        </provider>
        ...
    </application>
</manifest>

Pedir credenciais antes de mostrar informações confidenciais

Ao solicitar credenciais dos usuários para que eles possam acessar informações confidenciais ou conteúdo premium no app, peça um PIN/senha/padrão ou credencial biométrica, por exemplo, reconhecimento facial ou impressão digital.

Para saber mais sobre como solicitar credenciais biométricas, consulte o guia sobre autenticação biométrica.

Aplicar medidas de segurança de rede

As seções a seguir descrevem como melhorar a segurança de rede do seu app.

Usar o tráfego TLS

Caso seu app se comunique com um servidor da Web que tenha um certificado emitido por uma autoridade de certificação (CA, na sigla em inglês) conhecida e confiável, use uma solicitação HTTPS como esta:

Kotlin

val url = URL("https://www.google.com")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.connect()
urlConnection.inputStream.use {
    ...
}

Java

URL url = new URL("https://www.google.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.connect();
InputStream in = urlConnection.getInputStream();

Adicionar uma configuração de segurança de rede

Se seu app usa CAs novas ou personalizadas, você pode declarar as configurações de segurança da rede em um arquivo de configuração. Esse processo permite que você crie a configuração sem modificar nenhum código do app.

Para adicionar um arquivo de configuração de segurança de rede ao app, siga estas etapas:

  1. Declare a configuração no manifesto do app:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
  3. Adicione um arquivo de recurso XML, localizado em res/xml/network_security_config.xml.

    Especifique que todo o tráfego para domínios particulares precisa usar HTTPS desativando a função de limpar texto:

    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">secure.example.com</domain>
            ...
        </domain-config>
    </network-security-config>

    Durante o processo de desenvolvimento, é possível usar o elemento <debug-overrides> para permitir de maneira explícita certificados instalados pelo usuário. Esse elemento modifica as opções básicas de segurança do app durante a depuração e os testes, sem afetar a configuração da versão do app. O snippet a seguir mostra como definir esse elemento no arquivo XML da configuração de segurança de rede do app.

    <network-security-config>
        <debug-overrides>
            <trust-anchors>
                <certificates src="user" />
            </trust-anchors>
        </debug-overrides>
    </network-security-config>

Informações relacionadas: Configurações de segurança de rede

Criar o próprio gerenciador de confiança

Seu verificador de TLS não pode aceitar qualquer certificado. Se uma das seguintes condições for válida para seu caso de uso, pode ser necessário configurar um gerenciador de confiança e processar todos os alertas de TLS que ocorrerem:

  • Você se comunica com um servidor da Web que tem um certificado assinado por uma CA nova ou personalizada.
  • O dispositivo usado não confia na CA.
  • Não é possível usar uma configuração de segurança de rede.

Para saber mais sobre como seguir essas etapas, consulte a discussão sobre como processar uma autoridade de certificação desconhecida.

Informações relacionadas:

Usar objetos WebView com cuidado

Objetos WebView no seu app não podem autorizar os usuários a acessar sites que estão fora do seu controle. Sempre que possível, use uma lista de permissões para restringir o conteúdo carregado pelos objetos WebView do app.

Além disso, não ative o suporte à interface JavaScript, a menos que você controle e confie totalmente no conteúdo dos objetos WebView do seu app.

Usar canais de mensagem HTML

Caso seu app precise usar compatibilidade com a interface JavaScript em dispositivos com o Android 6.0 (API de nível 23) ou mais recente, use canais de mensagem HTML em vez de se comunicar entre um site e o app, conforme mostrado no snippet de código a seguir:

Kotlin

val myWebView: WebView = findViewById(R.id.webview)

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel()

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() {

    override fun onMessage(port: WebMessagePort, message: WebMessage) {
        Log.d(TAG, "On port $port, received this message: $message")
    }
})

// Send a message from channel[1] to channel[0].
channel[1].postMessage(WebMessage("My secure message"))

Java

WebView myWebView = (WebView) findViewById(R.id.webview);

// channel[0] and channel[1] represent the two ports.
// They are already entangled with each other and have been started.
WebMessagePort[] channel = myWebView.createWebMessageChannel();

// Create handler for channel[0] to receive messages.
channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
    @Override
    public void onMessage(WebMessagePort port, WebMessage message) {
         Log.d(TAG, "On port " + port + ", received this message: " + message);
    }
});

// Send a message from channel[1] to channel[0].
channel[1].postMessage(new WebMessage("My secure message"));

Informações relacionadas:

Oferecer as permissões corretas

Solicite apenas o número mínimo de permissões necessárias para que o app funcione corretamente. Quando possível, renuncie às permissões quando o app não precisar mais delas.

Usar intents para adiar permissões

Sempre que possível, não adicione uma permissão ao app para concluir uma ação que pode ser concluída em outro app. Em vez disso, use uma intent para adiar a solicitação para outro app que já tenha a permissão necessária.

O exemplo a seguir mostra como usar uma intent para direcionar os usuários a um app de contatos, em vez de solicitar as permissões READ_CONTACTS e WRITE_CONTACTS:

Kotlin

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent(Intent.ACTION_INSERT).apply {
    type = ContactsContract.Contacts.CONTENT_TYPE
}.also { intent ->
    // Make sure that the user has a contacts app installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

Java

// Delegates the responsibility of creating the contact to a contacts app,
// which has already been granted the appropriate WRITE_CONTACTS permission.
Intent insertContactIntent = new Intent(Intent.ACTION_INSERT);
insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);

// Make sure that the user has a contacts app installed on their device.
if (insertContactIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(insertContactIntent);
}

Além disso, se o app precisar realizar E/S com base em arquivos, por exemplo, acessar o armazenamento ou escolher um arquivo, não serão necessárias permissões especiais, porque o sistema poderá concluir as operações por ele. Melhor ainda, depois que o usuário seleciona o conteúdo em um URI específico, o app de chamada recebe a permissão para o recurso selecionado.

Informações relacionadas:

Compartilhar dados de forma segura entre apps

Siga estas práticas recomendadas para compartilhar conteúdos do seu app com outros apps de forma mais segura:

O snippet de código a seguir mostra como usar sinalizações de concessão de permissões do URI e permissões de provedor de conteúdo para mostrar o arquivo PDF do app em um app visualizador de PDF separado.

Kotlin

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse("content://com.example/personal-info.pdf")

    // This flag gives the started app read access to the file.
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}.also { intent ->
    // Make sure that the user has a PDF viewer app installed on their device.
    intent.resolveActivity(packageManager)?.run {
        startActivity(intent)
    }
}

Java

// Create an Intent to launch a PDF viewer for a file owned by this app.
Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW);
viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf"));

// This flag gives the started app read access to the file.
viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// Make sure that the user has a PDF viewer app installed on their device.
if (viewPdfIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(viewPdfIntent);
}

Observação: executar arquivos do diretório inicial do app gravável é uma violação W^X. Por isso, apps não confiáveis direcionados ao Android 10 (API de nível 29) e versões mais recentes não podem invocar exec() em arquivos no diretório inicial do app, apenas o código binário incorporado ao arquivo APK do app. Além disso, apps direcionados ao Android 10 e versões mais recentes não podem, na memória, modificar o código executável a partir de arquivos que foram abertos com dlopen(). Isso inclui todos os arquivos de objeto compartilhado (.so) com realocações de texto.

Informações relacionadas: android:grantUriPermissions

Armazenar dados de forma segura

Embora seu app possa ter que acessar informações confidenciais dos usuários, eles só vão conceder acesso a esses dados se confiarem que você vai protegê-los de forma adequada.

Armazenar dados privados no armazenamento interno

Armazene todos os dados particulares do usuário no armazenamento interno do dispositivo, que é colocado no sandbox por app. Seu app não precisa solicitar uma permissão para ver esses arquivos e outros apps não podem acessá-los. Como uma medida extra de segurança, quando o usuário desinstala um app, o dispositivo exclui todos os arquivos salvos pelo app no armazenamento interno.

O snippet de código abaixo demonstra uma forma de gravar dados no armazenamento interno:

Kotlin

// 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 FILE_NAME = "sensitive_info.txt"
val fileContents = "This is some top-secret information!"
File(filesDir, FILE_NAME).bufferedWriter().use { writer ->
    writer.write(fileContents)
}

Java

// 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.
final String FILE_NAME = "sensitive_info.txt";
String fileContents = "This is some top-secret information!";
try (BufferedWriter writer =
             new BufferedWriter(new FileWriter(new File(getFilesDir(), FILE_NAME)))) {
    writer.write(fileContents);
} catch (IOException e) {
    // Handle exception.
}

O snippet de código abaixo mostra a operação inversa, lendo dados do armazenamento interno:

Kotlin

val FILE_NAME = "sensitive_info.txt"
val contents = File(filesDir, FILE_NAME).bufferedReader().useLines { lines ->
    lines.fold("") { working, line ->
        "$working\n$line"
    }
}

Java

final String FILE_NAME = "sensitive_info.txt";
StringBuffer stringBuffer = new StringBuffer();
try (BufferedReader reader =
             new BufferedReader(new FileReader(new File(getFilesDir(), FILE_NAME)))) {

    String line = reader.readLine();
    while (line != null) {
        stringBuffer.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    // Handle exception.
}

Informações relacionadas:

Guardar dados no armazenamento externo com base no caso de uso

Use o armazenamento externo para arquivos grandes e não confidenciais específicos do seu app, assim como arquivos compartilhados com outros apps. As APIs específicas que você usa dependem do app ter sido projetado para acessar arquivos específicos do app ou acessar arquivos compartilhados.

Se um arquivo não contiver informações particulares ou confidenciais, mas tiver valor para o usuário apenas no app, armazene o arquivo em um diretório específico do app no armazenamento externo.

Caso seu app precise acessar ou armazenar um arquivo que tenha valor para outros apps, use uma das seguintes APIs, dependendo do seu caso de uso:

Verificar a disponibilidade do volume de armazenamento

Caso seu app interaja com um dispositivo de armazenamento externo removível, lembre-se de que o usuário pode remover o dispositivo de armazenamento enquanto o app estiver tentando acessá-lo. Inclua uma lógica para verificar se o dispositivo de armazenamento está disponível.

Verificar a validade dos dados

Caso seu app use dados de armazenamento externo, verifique se o conteúdo dos dados não foi corrompido ou modificado. Inclua uma lógica para processar arquivos que não estejam mais em um formato estável.

O snippet de código a seguir inclui um exemplo de verificador de hash.

Kotlin

val hash = calculateHash(stream)
// Store "expectedHash" in a secure location.
if (hash == expectedHash) {
    // Work with the content.
}

// Calculating the hash code can take quite a bit of time, so it shouldn't
// be done on the main thread.
suspend fun calculateHash(stream: InputStream): String {
    return withContext(Dispatchers.IO) {
        val digest = MessageDigest.getInstance("SHA-512")
        val digestStream = DigestInputStream(stream, digest)
        while (digestStream.read() != -1) {
            // The DigestInputStream does the work; nothing for us to do.
        }
        digest.digest().joinToString(":") { "%02x".format(it) }
    }
}

Java

Executor threadPoolExecutor = Executors.newFixedThreadPool(4);
private interface HashCallback {
    void onHashCalculated(@Nullable String hash);
}

boolean hashRunning = calculateHash(inputStream, threadPoolExecutor, hash -> {
    if (Objects.equals(hash, expectedHash)) {
        // Work with the content.
    }
});

if (!hashRunning) {
    // There was an error setting up the hash function.
}

private boolean calculateHash(@NonNull InputStream stream,
                              @NonNull Executor executor,
                              @NonNull HashCallback hashCallback) {
    final MessageDigest digest;
    try {
        digest = MessageDigest.getInstance("SHA-512");
    } catch (NoSuchAlgorithmException nsa) {
        return false;
    }

    // Calculating the hash code can take quite a bit of time, so it shouldn't
    // be done on the main thread.
    executor.execute(() -> {
        String hash;
        try (DigestInputStream digestStream =
                new DigestInputStream(stream, digest)) {
            while (digestStream.read() != -1) {
                // The DigestInputStream does the work; nothing for us to do.
            }
            StringBuilder builder = new StringBuilder();
            for (byte aByte : digest.digest()) {
                builder.append(String.format("%02x", aByte)).append(':');
            }
            hash = builder.substring(0, builder.length() - 1);
        } catch (IOException e) {
            hash = null;
        }

        final String calculatedHash = hash;
        runOnUiThread(() -> hashCallback.onHashCalculated(calculatedHash));
    });
    return true;
}

Armazenar somente dados não confidenciais em arquivos em cache

Para oferecer um acesso mais rápido a dados não confidenciais do app, armazene-os no cache do dispositivo. Para caches maiores que 1 MB, use getExternalCacheDir(). Para caches com 1 MB ou menos, use getCacheDir(). Ambos os métodos disponibilizam o objeto File, que contém os dados em cache do app.

O snippet de código a seguir mostra como armazenar em cache um arquivo transferido por download recentemente.

Kotlin

val cacheFile = File(myDownloadedFileUri).let { fileToCache ->
    File(cacheDir.path, fileToCache.name)
}

Java

File cacheDir = getCacheDir();
File fileToCache = new File(myDownloadedFileUri);
String fileToCacheName = fileToCache.getName();
File cacheFile = new File(cacheDir.getPath(), fileToCacheName);

Observação: se você usa getExternalCacheDir() para colocar o cache do app em armazenamento compartilhado, o usuário pode ejetar a mídia que contém esse armazenamento enquanto o app está em execução. Inclua uma lógica para processar adequadamente a ausência de cache que esse comportamento do usuário causa.

Cuidado: nenhum procedimento de segurança é aplicado a esses arquivos. Portanto, qualquer app direcionado ao Android 10 (API de nível 29) ou a uma versão anterior que tenha a permissão WRITE_EXTERNAL_STORAGE pode acessar o conteúdo desse cache.

Informações relacionadas: Visão geral do armazenamento de dados e arquivos

Usar SharedPreferences no modo privado

Ao usar getSharedPreferences() para criar ou acessar os objetos SharedPreferences do app, use MODE_PRIVATE. Dessa forma, apenas seu app poderá acessar as informações no arquivo de preferências compartilhadas.

Caso queira compartilhar dados entre apps, não use objetos SharedPreferences. Em vez disso, siga as etapas para compartilhar dados entre apps de forma segura.

A biblioteca Security também oferece a classe EncryptedSharedPreferences, que une a classe SharedPreferences e criptografa chaves e valores automaticamente.

Informações relacionadas:

Manter os serviços e as dependências atualizados

A maior parte dos apps usa bibliotecas externas e informações do sistema do dispositivo para concluir tarefas específicas. Mantendo as dependências do seu app atualizadas, você torna esses pontos de comunicação mais seguros.

Verificar o provedor de segurança do Google Play Services

Observação: esta seção só é válida para apps destinados a dispositivos em que o Google Play Services esteja instalado.

Caso seu app use o Google Play Services, verifique se ele está atualizado no dispositivo em que o app está instalado. Faça a verificação de forma assíncrona, fora da linha de execução de interface. Se o dispositivo não estiver atualizado, acione um erro de autorização.

Para determinar se o Google Play Services está atualizado no dispositivo, siga as etapas do guia sobre como Atualizar seu provedor de segurança para se proteger contra explorações de SSL.

Informações relacionadas:

Atualizar todas as dependências do app

Antes de implantar seu app, verifique se todas as bibliotecas, SDKs e outras dependências estão em dia:

  • Para dependências primárias, como o SDK do Android, use as ferramentas de atualização do Android Studio, como o SDK Manager.
  • Para dependências de terceiros, verifique os sites das bibliotecas usadas pelo app e instale todas as atualizações e patches de segurança disponíveis.

Informações relacionadas: Adicionar dependências de build

Mais informações

Para saber mais sobre como aumentar a segurança do seu app, consulte estes recursos: