Migliora la sicurezza della tua app

Rendendo la tua app più sicura, contribuisci a preservare la fiducia degli utenti e l'integrità del dispositivo.

Questa pagina presenta diverse best practice che hanno un impatto positivo significativo sulla sicurezza della tua app.

Applicare la comunicazione sicura

Quando salvaguardi i dati scambiati tra la tua app e altre app o tra la tua app e un sito web, migliori la stabilità della tua app e proteggi i dati che invii e ricevi.

Proteggere la comunicazione tra le app

Per comunicare tra le app in modo più sicuro, utilizza intent impliciti con un selettore di app, autorizzazioni basate su firme e fornitori di contenuti non esportati.

Mostrare un selettore di app

Se un'intent implicita può avviare almeno due app possibili sul dispositivo di un utente, mostra esplicitamente un selettore di app. Questa strategia di interazione consente agli utenti di trasferire informazioni sensibili a un'app di cui si fidano.

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

Informazioni correlate:

Applicare autorizzazioni basate su firme

Quando condividi dati tra due app che controlli o possiedi, utilizza le autorizzazioni basate su firme. Queste autorizzazioni non richiedono conferma da parte dell'utente, ma verificano che le app che accedono ai dati siano firmate utilizzando la stessa chiave di firma. Pertanto, queste autorizzazioni offrono un'esperienza utente più semplificata e sicura.

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

Informazioni correlate:

Non consentire l'accesso ai fornitori di contenuti della tua app

A meno che tu non intenda inviare dati dalla tua app a un'altra app che non possiedi, non consentire esplicitamente alle app di altri sviluppatori di accedere agli oggetti ContentProvider della tua app. Questa impostazione è particolarmente importante se la tua app può essere installata su dispositivi con sistema operativo Android 4.1.1 (livello API 16) o versioni precedenti, poiché l'attributo android:exported dell'elemento <provider> è true per impostazione predefinita su queste versioni di 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>

Chiedere le credenziali prima di mostrare informazioni sensibili

Quando chiedi agli utenti le credenziali per accedere a informazioni sensibili o contenuti premium nella tua app, chiedi un PIN/una password/una sequenza o una credenziale biometrica, come il riconoscimento del volto o delle impronte.

Per scoprire di più su come richiedere le credenziali biometriche, consulta la guida sull'autenticazione biometrica.

Applica misure di sicurezza della rete

Le sezioni seguenti descrivono come migliorare la sicurezza della rete della tua app.

Utilizzare il traffico TLS

Se la tua app comunica con un server web che dispone di un certificato emesso da un'autorità di certificazione (CA) attendibile e nota, utilizza una richiesta HTTPS come la seguente:

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

Aggiungere una configurazione di sicurezza di rete

Se la tua app utilizza CA nuove o personalizzate, puoi dichiarare le impostazioni di sicurezza della rete in un file di configurazione. Questa procedura ti consente di creare la configurazione senza modificare il codice dell'app.

Per aggiungere un file di configurazione della sicurezza di rete alla tua app, segui questi passaggi:

  1. Dichiara la configurazione nel file manifest dell'app:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
    
  3. Aggiungi un file di risorse XML, che si trova in res/xml/network_security_config.xml.

    Specifica che tutto il traffico verso determinati domini deve utilizzare HTTPS disattivando il testo non cifrato:

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

    Durante il processo di sviluppo, puoi utilizzare l'elemento <debug-overrides> per consentire esplicitamente i certificati installati dall'utente. Questo elemento sostituisce le opzioni di sicurezza critiche della tua app durante il debug e i test senza influire sulla configurazione della release dell'app. Lo snippet seguente mostra come definire questo elemento nel file XML di configurazione della sicurezza di rete dell'app:

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

Informazioni correlate: Configurazione della sicurezza della rete

Creare un gestore della attendibilità personalizzato

Lo strumento di controllo TLS non deve accettare tutti i certificati. Potresti dover configurare un gestore della attendibilità e gestire tutti gli avvisi TLS che si verificano se per il tuo caso d'uso si applica una delle seguenti condizioni:

  • Stai comunicando con un server web che ha un certificato firmato da un'autorità di certificazione nuova o personalizzata.
  • La CA non è attendibile per il dispositivo che stai utilizzando.
  • Non puoi utilizzare una configurazione di sicurezza di rete.

Per scoprire di più su come completare questi passaggi, consulta la discussione sulla gestione di un'autorità di certificazione sconosciuta.

Informazioni correlate:

Utilizzare con attenzione gli oggetti WebView

WebView Gli oggetti nella tua app non devono consentire agli utenti di accedere a siti al di fuori del tuo controllo. Se possibile, utilizza una lista consentita per limitare i contenuti caricati dagli oggetti WebView della tua app.

Inoltre, non attivare mai il supporto dell'interfaccia JavaScript, a meno che tu non controlli completamente e non ritenga attendibili i contenuti degli oggetti WebView della tua app.

Utilizzare i canali di messaggi HTML

Se la tua app deve utilizzare il supporto dell'interfaccia JavaScript sui dispositivi con Android 6.0 (livello API 23) e versioni successive, utilizza i canali di messaggi HTML anziché comunicare tra un sito web e la tua app, come mostrato nel seguente snippet di codice:

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

Informazioni correlate:

Fornire le autorizzazioni corrette

Richiedi solo il numero minimo di autorizzazioni necessarie per il corretto funzionamento della tua app. Se possibile, rinuncia alle autorizzazioni quando la tua app non ne ha più bisogno.

Utilizzare le intenzioni per posticipare le autorizzazioni

Se possibile, non aggiungere un'autorizzazione alla tua app per completare un'azione che può essere completata in un'altra app. Utilizza invece un'intent per rimandare la richiesta a un'altra app che dispone già dell'autorizzazione necessaria.

Il seguente esempio mostra come utilizzare un'intent per indirizzare gli utenti a un'app Contatti anziché richiedere le autorizzazioni 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);
}

Inoltre, se la tua app deve eseguire I/O basate su file, ad esempio accedere allo spazio di archiviazione o scegliere un file, non ha bisogno di autorizzazioni speciali perché il sistema può completare le operazioni per conto dell'app. Ancora meglio, dopo che un utente seleziona i contenuti in un determinato URI, all'app chiamante viene concessa l'autorizzazione per la risorsa selezionata.

Informazioni correlate:

Condividere dati in sicurezza tra le app

Segui queste best practice per condividere i contenuti della tua app con altre app in modo più sicuro:

Il seguente snippet di codice mostra come utilizzare i flag di concessione delle autorizzazioni URI e le autorizzazioni dei fornitori di contenuti per visualizzare il file PDF di un'app in un'app di visualizzazione PDF separata:

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

Nota: l'esecuzione di file dalla home directory dell'app scrivibile costituisce una violazione W^X. Per questo motivo, le app non attendibili che hanno come target Android 10 (livello API 29) e versioni successive non possono invocare exec() sui file all'interno della home directory dell'app, ma solo sul codice binario incorporato nel file APK di un'app. Inoltre, le app che hanno come target Android 10 e versioni successive non possono, in memoria, modificare il codice eseguibile dei file aperti con dlopen(). Sono inclusi tutti i file di oggetti condivisi (.so) con spostamenti di testo.

Informazioni correlate: android:grantUriPermissions

Memorizza i dati in sicurezza

Sebbene la tua app possa richiedere l'accesso a informazioni utente sensibili, gli utenti grantono all'app l'accesso ai propri dati solo se ritengono che tu li salvaguardi adeguatamente.

Memorizza i dati privati nello spazio di archiviazione interno

Memorizza tutti i dati utente privati nella memoria interna del dispositivo, che è in sandbox per app. La tua app non deve richiedere l'autorizzazione per visualizzare questi file e le altre app non possono accedervi. Come misura di sicurezza aggiuntiva, quando l'utente disinstalla un'app, il dispositivo elimina tutti i file salvati dall'app all'interno dello spazio di archiviazione interno.

Il seguente snippet di codice mostra un modo per scrivere dati nello spazio di archiviazione 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.
}

Il seguente snippet di codice mostra l'operazione inversa, ovvero la lettura dei dati dall'archiviazione interna:

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

Informazioni correlate:

Archivia i dati in uno spazio di archiviazione esterno in base al caso d'uso

Utilizza lo spazio di archiviazione esterno per file di grandi dimensioni e non sensibili specifici della tua app, nonché per i file condivisi dalla tua app con altre app. Le API specifiche che utilizzi dipendono dal fatto che la tua app sia progettata per accedere ai file specifici dell'app o ai file condivisi.

Se un file non contiene informazioni private o sensibili, ma offre valore all'utente solo nella tua app, archivialo in una directory specifica per l'app su archiviazione esterna.

Se la tua app deve accedere o memorizzare un file che offre valore ad altre app, utilizza una delle seguenti API, a seconda del caso d'uso:

Verificare la disponibilità dello spazio di archiviazione

Se la tua app interagisce con un dispositivo di archiviazione esterno rimovibile, tieni presente che l'utente potrebbe rimuoverlo mentre la tua app tenta di accedervi. Includi la logica per verificare che il dispositivo di archiviazione sia disponibile.

Controllare la validità dei dati

Se la tua app utilizza dati archiviati in un dispositivo di archiviazione esterno, assicurati che i contenuti dei dati non siano stati danneggiati o modificati. Includi la logica per gestire i file che non sono più in un formato stabile.

Il seguente snippet di codice include un esempio di verificatore di 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;
}

Memorizza solo dati non sensibili nei file della cache

Per fornire un accesso più rapido ai dati non sensibili delle app, memorizzali nella cache del dispositivo. Per le cache più grandi di 1 MB, utilizza getExternalCacheDir(). Per le cache di dimensioni inferiori a 1 MB, utilizza getCacheDir(). Entrambi i metodi forniscono l'oggetto File che contiene i dati memorizzati nella cache della tua app.

Il seguente snippet di codice mostra come memorizzare nella cache un file scaricato di recente dalla tua app:

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

Nota: se utilizzi getExternalCacheDir() per memorizzare la cache della tua app nello spazio di archiviazione condiviso, l'utente potrebbe espellere i contenuti multimediali contenenti questo spazio di archiviazione mentre l'app è in esecuzione. Includi una logica per gestire in modo corretto la mancata corrispondenza della cache causata da questo comportamento dell'utente.

Attenzione : su questi file non viene applicata alcuna sicurezza. Pertanto, qualsiasi app che ha come target Android 10 (livello API 29) o versioni precedenti e dispone dell'autorizzazioneWRITE_EXTERNAL_STORAGE può accedere ai contenuti di questa cache.

Informazioni correlate: Panoramica dell'archiviazione di file e dati

Utilizzare SharedPreferences in modalità privata

Quando utilizzi getSharedPreferences() per creare o accedere agli oggetti SharedPreferences della tua app, utilizza MODE_PRIVATE. In questo modo, solo la tua app può accedere alle informazioni all'interno del file delle preferenze condivise.

Se vuoi condividere dati tra app, non utilizzare gli oggetti SharedPreferences. Segui invece i passaggi per condividere i dati in modo sicuro tra le app.

La libreria Security fornisce anche la classe EncryptedSharedPreferences che racchiude la classe SharedPreferences e cripta automaticamente chiavi e valori.

Informazioni correlate:

Mantieni aggiornati i servizi e le dipendenze

La maggior parte delle app utilizza librerie esterne e informazioni di sistema del dispositivo per completare attività specializzate. Mantenendo aggiornate le dipendenze dell'app, rendi più sicuri questi punti di comunicazione.

Controllare il fornitore di servizi di sicurezza di Google Play

Nota: questa sezione si applica solo alle app che hanno come target i dispositivi su cui sono installati i Servizi Google Play.

Se la tua app utilizza Google Play Services, assicurati che sia aggiornata sul dispositivo su cui è installata. Esegui il controllo in modo asincrono, al di fuori del thread dell'interfaccia utente. Se il dispositivo non è aggiornato, viene attivato un errore di autorizzazione.

Per determinare se Google Play Services è aggiornato sul dispositivo su cui è installata la tua app, segui i passaggi descritti nella guida sull'aggiornamento del fornitore di servizi di sicurezza per proteggerti dagli exploit SSL.

Informazioni correlate:

Aggiorna tutte le dipendenze delle app

Prima di eseguire il deployment dell'app, assicurati che tutte le librerie, gli SDK e le altre dipendenze siano aggiornati:

  • Per le dipendenze proprietarie, come l'SDK Android, utilizza gli strumenti di aggiornamento disponibili in Android Studio, come SDK Manager.
  • Per le dipendenze di terze parti, controlla i siti web delle librerie utilizzate dalla tua app e installa eventuali aggiornamenti e patch di sicurezza disponibili.

Informazioni correlate: Aggiungi le dipendenze di compilazione

Ulteriori informazioni

Per scoprire di più su come rendere più sicura la tua app, consulta le seguenti risorse: