O segundo Visualização do Desenvolvedor do Android 11 já está disponível, teste e compartilhe seu feedback.

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 se seu app pode 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

Se seu app precisa usar a compatibilidade com a interface JavaScript em dispositivos com o Android 6.0 (API de nível 23) e posterior, use canais de mensagem HTML em vez de evaluateJavascript() para a comunicação entre um site e seu app, conforme 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 app para concluir uma ação que poderia ser concluída em outro app. Em vez disso, use um intent para adiar a solicitação para outro app que já tenha a permissão necessária.

O exemplo a seguir mostra como usar um 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 posterior 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 (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 posterior 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.

O snippet de código a seguir 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 a seguir 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:

Usar o armazenamento interno com cuidado

Por padrão, o sistema Android não aplica restrições de segurança sobre dados no armazenamento externo, e não há garantias de que o meio de armazenamento em si permanecerá conectado ao dispositivo. Por esse motivo, aplique as medidas de segurança a seguir para oferecer um acesso seguro a informações no armazenamento externo.

Usar o acesso a diretórios com escopo

Se seu app precisa acessar somente um diretório específico do armazenamento externo do dispositivo, você pode usar o acesso a diretórios com escopo para limitar o acesso do app ao armazenamento externo do dispositivo. Para a conveniência dos usuários, o app deve salvar o URI de acesso ao diretório. Assim, eles não precisarão aprovar o acesso ao diretório sempre que o app tentar acessá-lo.

Observação: se você usa o acesso a diretórios com escopo com um diretório específico no armazenamento externo, é importante saber que o usuário pode ejetar a mídia que contém o armazenamento enquanto o app está em execução. Inclua uma lógica para processar corretamente a mudança no valor Environment.getExternalStorageState() retornado que esse comportamento do usuário causa.

O snippet de código a seguir usa o acesso a diretórios com escopo com o diretório de imagens no armazenamento compartilhado principal de um dispositivo:

Kotlin

    private const val PICTURES_DIR_ACCESS_REQUEST_CODE = 42

    ...

    private fun accessExternalPicturesDirectory() {
        val intent: Intent = (getSystemService(Context.STORAGE_SERVICE) as StorageManager)
                .primaryStorageVolume.createAccessIntent(Environment.DIRECTORY_PICTURES)
        startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE)
    }

    ...

    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
        if (requestCode == PICTURES_DIR_ACCESS_REQUEST_CODE && resultCode == Activity.RESULT_OK) {

            // User approved access to scoped directory.
            if (resultData != null) {
                val picturesDirUri: Uri = resultData.data

                // Save user's approval for accessing this directory
                // in your app.
                contentResolver.takePersistableUriPermission(
                        picturesDirUri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION
                )
            }
        }
    }
    

Java

    private static final int PICTURES_DIR_ACCESS_REQUEST_CODE = 42;

    private void accessExternalPicturesDirectory() {
      StorageManager sm =
              (StorageManager) getSystemService(Context.STORAGE_SERVICE);
      StorageVolume volume = sm.getPrimaryStorageVolume();
      Intent intent =
              volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
      startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE);
    }

    ...

    @Override
    public void onActivityResult(int requestCode, int resultCode,
            Intent resultData) {
        if (requestCode == PICTURES_DIR_ACCESS_REQUEST_CODE &&
                resultCode == Activity.RESULT_OK) {

            // User approved access to scoped directory.
            if (resultData != null) {
                Uri picturesDirUri = resultData.getData();

                // Save user's approval for accessing this directory
                // in your app.
                ContentResolver myContentResolver = getContentResolver();
                myContentResolver.takePersistableUriPermission(picturesDirUri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
        }
    }
    

Alerta: não passe null para createAccessIntent() sem necessidade, porque isso concede ao app acesso a todo o volume que StorageManager encontrar no app.

Informações relacionadas:

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.

O exemplo a seguir mostra a permissão e a lógica que verificam a validade de um arquivo:

AndroidManifest.xml

    <manifest ... >
        <!-- Apps on devices running Android 4.4 (API level 19) or higher cannot
             access external storage outside their own "sandboxed" directory, so
             the READ_EXTERNAL_STORAGE (and WRITE_EXTERNAL_STORAGE) permissions
             aren't necessary. -->
        <uses-permission
              android:name="android.permission.READ_EXTERNAL_STORAGE"
              android:maxSdkVersion="18" />
        ...
    </manifest>

    

MyFileValidityChecker

Kotlin

    private val UNAVAILABLE_STORAGE_STATES: Set<String> =
            setOf(MEDIA_REMOVED, MEDIA_UNMOUNTED, MEDIA_BAD_REMOVAL, MEDIA_UNMOUNTABLE)
    ...
    val ringtone = File(getExternalFilesDir(DIRECTORY_RINGTONES), "my_awesome_new_ringtone.m4a")
    when {
        isExternalStorageEmulated(ringtone) -> {
            Log.e(TAG, "External storage is not present")
        }
        UNAVAILABLE_STORAGE_STATES.contains(getExternalStorageState(ringtone)) -> {
            Log.e(TAG, "External storage is not available")
        }
        else -> {
            val fis = FileInputStream(ringtone)

            // available() determines the approximate number of bytes that
            // can be read without blocking.
            val bytesAvailable: Int = fis.available()
            val fileBuffer = ByteArray(bytesAvailable)
            StringBuilder(bytesAvailable).apply {
                while (fis.read(fileBuffer) != -1) {
                    append(fileBuffer)
                }
                // Implement appropriate logic for checking a file's validity.
                checkFileValidity(this)
            }
        }
    }
    

Java

    File ringtone = new File(getExternalFilesDir(DIRECTORY_RINGTONES,
            "my_awesome_new_ringtone.m4a"));
    if (isExternalStorageEmulated(ringtone)) {
        Logger.e(TAG, "External storage is not present");
    } else if (getExternalStorageState(ringtone) == MEDIA_REMOVED
            | MEDIA_UNMOUNTED | MEDIA_BAD_REMOVAL | MEDIA_UNMOUNTABLE) {
        Logger.e(TAG, "External storage is not available");
    } else {
        FileInputStream fis = new FileInputStream(ringtone);

        // available() determines the approximate number of bytes that
        // can be read without blocking.
        int bytesAvailable = fis.available();
        StringBuilder fileContents = new StringBuilder(bytesAvailable);
        byte[] fileBuffer = new byte[bytesAvailable];
        while (fis.read(fileBuffer) != -1) {
            fileContents.append(fileBuffer);
        }

        // Implement appropriate logic for checking a file's validity.
        checkFileValidity(fileContents);
    }
    

Informações relacionadas:

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.

Se você quiser 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