Práticas recomendadas para segurança de apps

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.

Usar intents implícitos e provedores de conteúdo não exportados

Mostrar um seletor de app

Se um intent implícito pode iniciar pelo menos dois apps possíveis 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 confidenciais para um app de confiança.

Kotlin

    val intent = Intent(ACTION_SEND)
    val possibleActivitiesList: List<ResolveInfo> =
            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 =
            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 seu app para outro que não seja seu, impeça de maneira explícita que os apps de outros desenvolvedores acessem os objetos ContentProvider no seu app. Essa configuração é importante especialmente caso seu app possa ser instalado em dispositivos com o Android 4.1.1 (API de nível 16) 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 exibir informações confidenciais

Ao solicitar as 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, como o uso de reconhecimento facial ou de 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 tráfego SSL

Se seu app se comunica com um servidor da Web que tem um certificado emitido por uma Autoridade de certificação (CA, na sigla em inglês) conhecida e confiável, a solicitação HTTPS é muito simples:

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 mudar qualquer código do app.

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

  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 deve 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, você pode usar o elemento <debug-overrides> para permitir explicitamente 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 SSL não deve aceitar qualquer certificado. Se uma das seguintes condições é válida para seu caso de uso, pode ser necessário configurar um gerenciador de confiança e processar todos os alertas de SSL 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.
  • Você não pode 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

Sempre que possível, carregue somente conteúdos autorizados em objetos WebView. Em outras palavras, os objetos WebView no seu app não devem autorizar os usuários a navegar em sites que estão fora do seu controle.

Além disso, nunca ative a compatibilidade com a 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 a compatibilidade com a interface JavaScript em dispositivos com o Android 6.0 (API de nível 23) e mais recentes, use canais de mensagem HTML, em vez de comunicação entre um site e seu app, como mostrado no snippet de código a seguir:

Kotlin

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

    // messagePorts[0] and messagePorts[1] represent the two ports.
    // They are already tangled to 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);

    // messagePorts[0] and messagePorts[1] represent the two ports.
    // They are already tangled to 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

Seu app deve solicitar somente o número mínimo de permissões necessárias para funcionar corretamente. Quando possível, ele deverá renunciar a algumas dessas permissões quando elas deixarem de ser necessárias.

Usar intents para adiar permissões

Sempre que possível, não adicione uma permissão ao seu app para concluir uma ação que poderia 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 seu app precisa realizar E/S com base em arquivos, por exemplo, acessar o armazenamento ou escolher um arquivo, não são necessárias permissões especiais, porque o sistema pode concluir as operações por ele. Melhor ainda, depois que o usuário selecionar o conteúdo em um URI específico, o app de chamada receberá 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 exibir 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: apps não confiáveis que segmentam o Android 10 (API de nível 29) e versões mais recentes não podem invocar exec() em arquivos dentro do diretório inicial do app. Essa execução de arquivos a partir do diretório inicial do app gravável é uma violação W^X (link em inglês). Os apps devem carregar somente o código binário incorporado ao arquivo do APK do app. Além disso, os apps que segmentam o Android 10 e versões mais recentes não podem modificar o código executável na memória 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ó concederão acesso a esses dados se confiarem que você os protegerá de forma adequada.

Armazenar dados privados no armazenamento interno

Armazene todos os dados privados 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.

Observação: se os dados que você está armazenando são especialmente confidenciais ou particulares, trabalhe com objetos EncryptedFile, que estão disponíveis na biblioteca Security , em vez de objetos File.

O snippet de código a seguir mostra uma forma de gravar dados no armazenamento:

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

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

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"
    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";
    EncryptedFile encryptedFile = new EncryptedFile.Builder(
            new 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();
    }
    

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 ao 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.

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 está tentando acessá-lo. Inclua uma lógica para verificar se o dispositivo de armazenamento está disponível.

Acessar arquivos específicos do app

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.

Acessar arquivos compartilhados

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 validade dos dados

Se seu app usa dados do armazenamento externo, verifique se o conteúdo dos dados não foram corrompidos ou modificados. Seu app também deve incluir uma lógica para processar arquivos que não estão mais em um formato estável.

Um exemplo de verificador de hash é mostrado no snippet de código a seguir:

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(). Caso contrário, use getCacheDir(). Cada método disponibiliza 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 no 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 corretamente a ausência no cache que esse comportamento do usuário causa.

Cuidado: nenhum procedimento de segurança é aplicado a esses arquivos. Portanto, qualquer app que tenha a permissão WRITE_EXTERNAL_STORAGE pode acessar o conteúdo desse cache.

Informações relacionadas: Como salvar arquivos em cache

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. Siga as etapas necessárias para compartilhar dados entre apps de forma segura.

Informações relacionadas: Como usar preferências compartilhadas

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 que têm o Google Play Services instalado.

Se seu app usa o Google Play Services, verifique se ele está atualizado no dispositivo em que seu app está instalado. Essa verificação deve ser feita de forma assíncrona, fora da linha de execução de IU. Se o dispositivo não estiver atualizado, o app precisará acionar um erro de autorização.

Para determinar se o Google Play Services está atualizado no dispositivo em que o app está instalado, siga as etapas do guia 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 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 compilação

Mais informações

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

Outros recursos

Para ver mais informações sobre como deixar seu app mais seguro, consulte estes recursos:

Codelabs

Blogs