Améliorer la sécurité de votre application

En renforçant la sécurité de votre application, vous contribuez à préserver la confiance des utilisateurs et l'intégrité des appareils.

Cette page présente plusieurs bonnes pratiques qui ont un impact significatif et positif sur la sécurité de votre application.

Appliquer une communication sécurisée

Lorsque vous sauvegardez les données que vous échangez entre votre application et d'autres applications, ou entre votre application et un site Web, vous améliorez la stabilité de votre application et protégez les données que vous envoyez et recevez.

Protéger la communication entre les applis

Pour une communication plus sécurisée entre les applis, utilisez des intents implicites avec un sélecteur d'application, des autorisations basées sur la signature et des fournisseurs de contenu non exportés.

Afficher un sélecteur d'application

Si un intent implicite peut lancer au moins deux applications possibles sur l'appareil d'un utilisateur, affichez explicitement un sélecteur d'application. Cette stratégie d'interaction permet aux utilisateurs de transférer des informations sensibles vers une application de confiance.

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

Informations connexes :

Appliquer des autorisations basées sur la signature

Lorsque vous partagez des données entre deux applications que vous contrôlez ou possédez, utilisez des autorisations basées sur les signatures. Ces autorisations ne nécessitent pas de confirmation de l'utilisateur. Elles vérifient à la place que les applications accédant aux données sont signées à l'aide de la même clé de signature. Par conséquent, ces autorisations offrent une expérience utilisateur simplifiée et sécurisée.

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

Informations connexes :

Interdire l'accès aux fournisseurs de contenu de votre appli

À moins que vous n'ayez l'intention d'envoyer des données de votre appli vers une autre appli dont vous n'êtes pas propriétaire, interdisez explicitement aux applis d'autres développeurs d'accéder aux objets ContentProvider que contient la vôtre. Ce paramètre est particulièrement important si votre application peut être installée sur des appareils équipés d'Android 4.1.1 (niveau d'API 16) ou version antérieure, car l'attribut android:exported de l'élément <provider> est true par défaut sur ces versions d'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>

Demander des identifiants avant d'afficher des informations sensibles

Lorsque vous demandez aux utilisateurs des identifiants pour qu'ils puissent accéder à des informations sensibles ou à du contenu premium dans votre application, demandez un code/mot de passe/schéma ou un identifiant biométrique (comme la reconnaissance faciale ou d'empreinte digitale).

Pour en savoir plus sur la demande d'identifiants biométriques, consultez le guide sur l'authentification biométrique.

Appliquer des mesures de sécurité réseau

Les sections suivantes décrivent comment améliorer la sécurité réseau de votre application.

Utiliser le trafic TLS

Si votre appli communique avec un serveur Web disposant d'un certificat émis par une autorité de certification (CA) bien connue, utilisez une requête HTTPS semblable à celle-ci :

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

Ajouter une configuration de sécurité réseau

Si votre application utilise de nouvelles CA ou des CA personnalisées, vous pouvez déclarer les paramètres de sécurité de votre réseau dans un fichier de configuration. Ce processus vous permet de créer la configuration sans modifier le code de l'appli.

Pour ajouter un fichier de configuration de sécurité réseau à votre application, procédez comme suit :

  1. Déclarez la configuration dans le fichier manifeste de votre application :
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
  3. Ajoutez un fichier de ressources XML, situé dans res/xml/network_security_config.xml.

    Spécifiez que tout le trafic vers des domaines spécifiques doit utiliser HTTPS en désactivant le texte clair :

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

    Au cours du processus de développement, vous pouvez utiliser l'élément <debug-overrides> pour autoriser explicitement les certificats installés par l'utilisateur. Cet élément ignore les options de sécurité critiques de votre application lors du débogage et des tests sans affecter la configuration des versions de l'application. L'extrait de code suivant montre comment définir cet élément dans le fichier XML de configuration de la sécurité réseau de votre application :

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

Informations connexes : Configuration de la sécurité réseau

Créer votre propre gestionnaire de confiance

Votre vérificateur TLS ne doit pas accepter tous les certificats. Vous devrez peut-être configurer un gestionnaire de confiance et gérer tous les avertissements TLS qui se produisent si l'une des conditions suivantes s'applique à votre cas d'utilisation :

  • Vous communiquez avec un serveur Web disposant d'un certificat signé par une autorité de certification nouvelle ou personnalisée.
  • Cette autorité de certification n'est pas approuvée par l'appareil que vous utilisez.
  • Vous ne pouvez pas utiliser de configuration de sécurité réseau.

Pour en savoir plus sur la procédure à suivre, consultez la discussion sur la gestion d'une autorité de certification inconnue.

Informations connexes :

Utiliser les objets WebView avec soin

Les objets WebView de votre application ne doivent pas autoriser les utilisateurs à accéder à des sites que vous ne contrôlez pas. Dans la mesure du possible, utilisez une liste d'autorisation pour limiter le contenu chargé par les objets WebView de votre appli.

De plus, n'activez jamais la prise en charge de l'interface JavaScript, sauf si vous contrôlez complètement le contenu des objets WebView de votre appli et que vous faites confiance à ce contenu.

Utiliser des canaux de message HTML

Si votre application doit utiliser l'interface JavaScript sur les appareils équipés d'Android 6.0 (niveau d'API 23) ou version ultérieure, utilisez des canaux de message HTML au lieu de communiquer entre un site Web et votre application, comme indiqué dans l'extrait de code suivant :

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

Informations connexes :

Fournir les autorisations appropriées

Demandez uniquement le nombre minimal d'autorisations nécessaires au bon fonctionnement de votre application. Lorsque cela est possible, renoncez aux autorisations lorsque votre appli n'en a plus besoin.

Utiliser des intents pour différer les autorisations

Dans la mesure du possible, n'ajoutez pas d'autorisation à votre appli pour effectuer une action qui peut être réalisée dans une autre. Utilisez plutôt un intent pour différer la requête vers une autre appli qui dispose déjà de l'autorisation nécessaire.

L'exemple suivant montre comment utiliser un intent pour diriger les utilisateurs vers une application Contacts au lieu de demander les autorisations READ_CONTACTS et 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);
}

En outre, si votre application doit effectuer des E/S basées sur des fichiers, comme accéder au stockage ou choisir un fichier, elle n'a pas besoin d'autorisations spéciales, car le système peut effectuer les opérations pour le compte de votre application. Mieux encore, une fois qu'un utilisateur a sélectionné du contenu à un URI particulier, l'application appelante est autorisée à accéder à la ressource sélectionnée.

Informations connexes :

Partager des données de façon sécurisée entre les applications

Suivez ces bonnes pratiques pour partager le contenu de votre application avec d'autres applications de manière plus sécurisée :

L'extrait de code suivant montre comment utiliser les indicateurs d'octroi d'autorisation d'URI et les autorisations du fournisseur de contenu pour afficher le fichier PDF d'une application dans une application de lecteur de PDF distincte :

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

Remarque : L'exécution de fichiers à partir du répertoire d'accueil de l'application accessible en écriture est un cas de non-respect de la règle W^X. Pour cette raison, les applis non approuvées qui ciblent Android 10 (niveau d'API 29) ou version ultérieure ne peuvent pas appeler exec() sur les fichiers du répertoire d'accueil de l'appli, mais uniquement le code binaire intégré dans le fichier APK d'une appli. En outre, les applis qui ciblent Android 10 et versions ultérieures ne peuvent pas modifier le code exécutable en mémoire des fichiers ouverts avec dlopen(). Cela inclut tous les fichiers d'objets partagés (.so) associés à des transferts de texte.

Informations connexes : android:grantUriPermissions

Stocker les données de façon sécurisée

Bien que votre application puisse nécessiter l'accès à des informations utilisateur sensibles, les utilisateurs n'autorisent votre application à accéder à leurs données que s'ils sont sûrs que vous les protégez correctement.

Stocker des données privées dans la mémoire de stockage interne

Stockez toutes les données utilisateur privées dans la mémoire de stockage interne de l'appareil, qui est placée dans un bac à sable par application. Votre application n'a pas besoin de demander l'autorisation de consulter ces fichiers, et les autres applications ne peuvent pas accéder aux fichiers. Par mesure de sécurité supplémentaire, lorsque l'utilisateur désinstalle une application, l'appareil supprime tous les fichiers enregistrés par l'application dans la mémoire de stockage interne.

L'extrait de code suivant présente une façon d'écrire des données dans l'espace de stockage interne :

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

L'extrait de code suivant montre l'opération inverse, qui consiste à lire les données de stockage interne :

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

Informations connexes :

Stocker des données dans un espace de stockage externe en fonction du cas d'utilisation

Utilisez un espace de stockage externe pour les fichiers volumineux et non sensibles spécifiques à votre application, ainsi que pour les fichiers que votre application partage avec d'autres applications. Les API spécifiques que vous utilisez varient selon que votre application est conçue pour accéder aux fichiers spécifiques à l'application ou aux fichiers partagés.

Si un fichier ne contient pas d'informations privées ou sensibles, mais qu'il n'offre de valeur à votre utilisateur que dans votre application, stockez-le dans un répertoire spécifique à l'application sur un espace de stockage externe.

Si votre application doit accéder à un fichier ou le stocker pour apporter de la valeur à d'autres applications, utilisez l'une des API suivantes en fonction de votre cas d'utilisation :

Vérifier la disponibilité du volume de stockage

Si votre application interagit avec un appareil de stockage externe amovible, n'oubliez pas que l'utilisateur peut le retirer pendant que votre application tente d'y accéder. Incluez une logique pour vérifier que le périphérique de stockage est disponible.

Vérifier la validité des données

Si votre application utilise des données d'un espace de stockage externe, assurez-vous que le contenu de ces données n'a pas été corrompu ni modifié. Incluez une logique pour gérer les fichiers qui ne sont plus dans un format stable.

L'extrait de code suivant inclut un exemple d'outil de vérification de hachage :

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

Stocker uniquement les données non sensibles dans des fichiers cache

Pour accélérer l'accès aux données d'application non sensibles, stockez-les dans le cache de l'appareil. Pour les caches d'une taille supérieure à 1 Mo, utilisez getExternalCacheDir(). Pour les caches de 1 Mo ou moins, utilisez getCacheDir(). Ces deux méthodes fournissent l'objet File qui contient les données mises en cache de votre application.

L'extrait de code suivant montre comment mettre en cache un fichier que votre application a téléchargé récemment :

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

Remarque : Si vous utilisez getExternalCacheDir() pour placer le cache de votre application dans un espace de stockage partagé, l'utilisateur peut éjecter le support contenant cet espace de stockage lorsque votre application est en cours d'exécution. Incluez une logique pour gérer correctement le défaut de cache (miss) causé par ce comportement de l'utilisateur.

Attention : Ces fichiers ne font pas l'objet d'une sécurité. Par conséquent, toute application qui cible Android 10 (niveau d'API 29) ou version antérieure et dispose de l'autorisation WRITE_EXTERNAL_STORAGE peut accéder au contenu de ce cache.

Informations connexes : Présentation du stockage des données et des fichiers

Utiliser SharedPreferences en mode privé

Si vous utilisez getSharedPreferences() pour créer ou accéder aux objets SharedPreferences de votre application, utilisez MODE_PRIVATE. De cette façon, seule votre application peut accéder aux informations du fichier de préférences partagées.

Si vous souhaitez partager des données entre plusieurs applications, n'utilisez pas d'objets SharedPreferences. Suivez plutôt les étapes nécessaires pour partager des données de manière sécurisée entre les applications.

La bibliothèque Security fournit également la classe EncryptedSharedPreferences qui encapsule la classe SharedPreferences, et chiffre automatiquement les clés et les valeurs.

Informations connexes :

Maintenir les services et les dépendances à jour

La plupart des applications utilisent des bibliothèques externes et des informations système sur l'appareil pour effectuer des tâches spécialisées. En maintenant les dépendances de votre application à jour, vous renforcez la sécurité de ces points de communication.

Vérifier le fournisseur de sécurité des services Google Play

Remarque : Cette section ne s'applique qu'aux applications ciblant les appareils sur lesquels les services Google Play sont installés.

Si votre application utilise les services Google Play, assurez-vous qu'ils sont mis à jour sur l'appareil sur lequel elle est installée. Effectuez la vérification de manière asynchrone, en dehors du thread UI. Si l'appareil n'est pas à jour, déclenchez une erreur d'autorisation.

Pour déterminer si les services Google Play sont à jour sur l'appareil sur lequel votre application est installée, suivez la procédure décrite dans le guide Mettre à jour votre fournisseur de sécurité pour vous protéger contre les exploits SSL.

Informations connexes :

Mettre à jour toutes les dépendances de l'application

Avant de déployer votre application, assurez-vous que l'ensemble des bibliothèques, des SDK et des autres dépendances sont à jour :

  • Pour les dépendances propriétaires, telles que le SDK Android, utilisez les outils de mise à jour d'Android Studio, tels que SDK Manager.
  • Pour les dépendances tierces, consultez les sites Web des bibliothèques utilisées par votre application, et installez les mises à jour et les correctifs de sécurité disponibles.

Informations connexes : Ajouter des dépendances de build

En savoir plus

Pour en savoir plus sur la sécurisation de votre application, consultez les ressources suivantes :