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 significativo e positivo sulla sicurezza della tua app.

Imponi comunicazioni sicure

Quando proteggi i dati che scambi 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.

Proteggi le comunicazioni tra le app

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

Mostra un selettore di app

Se un intent implicito può avviare almeno due possibili app 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."

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

Java

Intent intent = new Intent(Intent.ACTION_SEND);
List&lt;ResolveInfo&gt; 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."

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

Informazioni correlate:

Applica autorizzazioni basate sulla firma

Quando condividi dati tra due app che controlli o possiedi, utilizza le autorizzazioni basate sulla firma. Queste autorizzazioni non richiedono la conferma 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 provider di contenuti della tua app

A meno che tu non intenda inviare dati dalla tua app a un'altra app che non possiedi, vieta 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 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="androidx.core.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            ...
            android:exported="false">
            <!-- Place child elements of <provider> here. -->
        </provider>
        ...
    </application>
</manifest>

Chiedi le credenziali prima di mostrare informazioni sensibili

Quando richiedi le credenziali agli utenti per consentire loro di accedere a informazioni sensibili o contenuti premium nella tua app, chiedi un PIN, una password o una sequenza oppure una credenziale biometrica, ad esempio il riconoscimento facciale o dell'impronta.

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

Applica misure di sicurezza di rete

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

Utilizza il traffico TLS

Se la tua app comunica con un server web che ha 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();

Aggiungi 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. Questo processo 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 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 domini particolari deve utilizzare HTTPS disattivando il testo non crittografato:

    <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 critiche per la sicurezza della tua app durante il debug e i test senza influire sulla configurazione della release dell'app. Il seguente snippet mostra come definire questo elemento nel file XML di configurazione della sicurezza di rete della tua app:

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

Informazioni correlate: Configurazione della sicurezza di rete

Crea il tuo gestore di attendibilità

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

  • Stai comunicando con un server web che ha un certificato firmato da una CA nuova o personalizzata.
  • La CA non è considerata attendibile dal dispositivo che stai utilizzando.
  • Non puoi utilizzare una configurazione della 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:

Utilizza gli oggetti WebView con attenzione

Gli oggetti WebView nella tua app non devono consentire agli utenti di accedere a siti che non sono sotto il tuo controllo. Quando 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 e consideri completamente attendibili i contenuti negli oggetti WebView della tua app.

Utilizza i canali di messaggi HTML

Se la tua app deve utilizzare il supporto dell'interfaccia JavaScript su 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&lt;out WebMessagePort&gt; = 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:

Fornisci le autorizzazioni corrette

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

Utilizza gli intent 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.

L'esempio seguente 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 operazioni di 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. Inoltre, dopo che un utente seleziona i contenuti in un URI specifico, l'app di chiamate riceve l'autorizzazione per la risorsa selezionata.

Informazioni correlate:

Condividi i dati in modo sicuro 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 del provider 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("<b>content:</b>//com.example/personal-info.pdf")

    // This flag gives the started app read access to the file.
    <b>addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)</b>
}.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("<b>content://</b>com.example/personal-info.pdf"));

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

// 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 è 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 richiamare exec() nei file all'interno della home directory dell'app, ma solo il codice binario incorporato nel file APK di un'app. Inoltre, le app che hanno come target Android 10 e versioni successive non possono modificare in memoria il codice eseguibile dei file aperti con dlopen(). Sono inclusi tutti i file di oggetti condivisi (.so) con rilocazioni di testo.

Informazioni correlate: android:grantUriPermissions

Archivia i dati in sicurezza

Anche se la tua app potrebbe richiedere l'accesso a informazioni utente sensibili, gli utenti concedono all'app l'accesso ai propri dati solo se hanno la certezza che li proteggerai adeguatamente.

Archivia i dati privati nello spazio di archiviazione interno

Archivia tutti i dati utente privati nello spazio di archiviazione interno del dispositivo, che è in modalità sandbox per app. La tua app non deve richiedere l'autorizzazione per visualizzare questi file e le altre app non possono accedervi. Come ulteriore misura di sicurezza, quando l'utente disinstalla un'app, il dispositivo elimina tutti i file salvati dall'app nello 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 dallo spazio di archiviazione 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.
}

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 non sensibili specifici per la tua app, nonché per i file che la tua app condivide con altre app. Le API specifiche che utilizzi dipendono dal fatto che la tua app sia progettata per accedere a file specifici dell'app o a file condivisi.

Se un file non contiene informazioni private o sensibili, ma fornisce valore all'utente solo nella tua app, memorizzalo in una directory specifica per l'app nello spazio di archiviazione esterno.

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

Verifica la disponibilità del volume di archiviazione

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

Controlla la validità dei dati

Se la tua app utilizza dati provenienti da uno spazio 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 un verificatore di hash:

Kotlin

val hash = calculateHash(stream)
// Store "expectedHash" in a secure location.
if (hash == <var>expectedHash</var>) {
    // 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 -&gt; {
    if (Objects.equals(hash, <var>expectedHash</var>)) {
        // 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(() -&gt; {
        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;
}

Utilizza lo spazio di archiviazione interno per i dati sensibili della cache

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

Mentre la directory della cache interna (fornita da getCacheDir()) è privata per la tua app, la directory della cache esterna non lo è.

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 inserire la cache della tua app nello spazio di archiviazione condiviso, l'utente potrebbe espellere il supporto contenente questo spazio di archiviazione mentre l'app è in esecuzione. Includi la logica per gestire correttamente il fallimento della cache causato da questo comportamento dell'utente.

Attenzione: non viene applicata alcuna misura di sicurezza ai file nella directory della cache esterna. Pertanto, qualsiasi app che ha come target Android 10 (livello API 29) o versioni precedenti e dispone dell'autorizzazione WRITE_EXTERNAL_STORAGE può accedere ai contenuti di questa cache.

Informazioni correlate: Panoramica dell'archiviazione di dati e file

Utilizza 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 contenute nel file delle preferenze condivise.

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

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 della tua app, rendi più sicuri questi punti di comunicazione.

Controlla il provider di sicurezza di Google Play Services

Se la tua app utilizza Google Play Services, assicurati che sia aggiornato sul dispositivo su cui è installata l'app. Esegui il controllo in modo asincrono, al di fuori del thread dell'interfaccia utente. Se il dispositivo non è aggiornato, attiva 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 provider di sicurezza per proteggersi dagli exploit SSL.

Informazioni correlate:

Aggiorna tutte le dipendenze dell'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, ad esempio SDK Manager.
  • Per le dipendenze di terze parti, controlla i siti web delle librerie utilizzate dalla tua app e installa gli aggiornamenti e le patch di sicurezza disponibili.

Informazioni correlate: Aggiungi dipendenze di build

Ulteriori informazioni

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