Migliora la sicurezza della tua app

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

Applica la comunicazione sicura

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.

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 sulla firma e fornitori di contenuti non esportati.

Mostrare 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."

    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 le autorizzazioni basate sulla firma

Quando condividi dati tra due app che controlli o di cui sei proprietario, utilizza le autorizzazioni basate sulla firma. Queste autorizzazioni non richiedono la conferma dell'utente e verificano invece 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 di cui non sei proprietario, impedisci 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="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 richiedi le credenziali agli utenti per consentire loro di accedere a informazioni sensibili o contenuti premium nella tua app, chiedi un PIN/password/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 di rete della tua app.

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

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. 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 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 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 esegue l'override delle opzioni critiche per la sicurezza della tua app durante il debug e il 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 di rete

Crea il tuo gestore dell'attendibilità

Il controllo TLS non deve accettare tutti i certificati. Potresti dover configurare un trust manager 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 con un certificato firmato da una CA nuova o personalizzata.
  • L'autorità di certificazione 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 sezione relativa alla 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 navigare su siti al di fuori del 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 non ti fidi completamente dei contenuti negli oggetti WebView della tua app.

Utilizzare i canali di messaggistica 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<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 gli intent per posticipare le autorizzazioni

Quando 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 di 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 della tua 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 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:

Lo snippet di codice seguente mostra come utilizzare i flag di concessione dell'autorizzazione URI e le autorizzazioni del content provider 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 directory home dell'app scrivibile è una violazione di 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() 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 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

Memorizzare i dati in sicurezza

Anche se la tua app potrebbe richiedere l'accesso a informazioni utente sensibili, gli utenti concedono alla tua app l'accesso ai loro dati solo se si fidano che tu li protegga adeguatamente.

Memorizza i dati privati all'interno dell'archiviazione interna

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

Il seguente snippet di codice mostra un modo per scrivere dati nella memoria interna:

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 dalla memoria 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 l'archiviazione esterna per i file di grandi dimensioni e 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, archivialo in una directory specifica dell'app nella memoria esterna.

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

Controllare 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 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 provenienti da spazio di archiviazione esterno, assicurati che i contenuti 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 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, archiviali nella cache del dispositivo. Per le cache più grandi di 1 MB, utilizza getExternalCacheDir(). Per le cache di dimensioni pari o inferiori a 1 MB, utilizza getCacheDir(). Entrambi i metodi forniscono l'oggetto File che contiene i dati memorizzati nella cache dell'app.

Il seguente snippet di codice mostra come memorizzare nella cache un file scaricato di recente dall'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 dell'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 l'errore di mancata corrispondenza della cache causato da questo comportamento dell'utente.

Attenzione : non viene applicata alcuna sicurezza a questi file. 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 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 i dati tra le app, non utilizzare SharedPreferences oggetti. Segui invece i passaggi per condividere i dati in modo sicuro tra le app.

Informazioni correlate:

Mantenere aggiornati i servizi e le dipendenze

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

Controllare il fornitore di sicurezza di Google Play Services

Nota: questa sezione si applica solo alle app che hanno come target dispositivi con Google Play Services installati.

Se la tua app utilizza Google Play Services, assicurati che sia aggiornato sul dispositivo su cui è installata. Esegui il controllo in modo asincrono, al di fuori del thread UI. Se il dispositivo non è aggiornato, attiva un errore di autorizzazione.

Per determinare se Google Play Services è aggiornato sul dispositivo in cui è installata la tua app, segui i passaggi della guida su 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: Aggiungere dipendenze di build

Ulteriori informazioni

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