Évaluations de l'intégrité

Cette page explique comment interpréter et utiliser l'évaluation de l'intégrité renvoyée. Que vous effectuiez une requête API standard ou classique, l'évaluation de l'intégrité est renvoyée au même format avec un contenu similaire. L'évaluation de l'intégrité communique des informations sur la validité des appareils, des applications et des comptes. Le serveur de votre application peut utiliser la charge utile générée dans une évaluation déchiffrée et validée pour déterminer la meilleure façon de procéder pour une action ou une requête particulière dans votre application.

Format de l'évaluation de l'intégrité renvoyée

La charge utile est au format JSON en texte brut et contient des signaux d'intégrité en plus des informations fournies par le développeur.

La structure de la charge utile générale est la suivante :

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
  environmentDetails: { ... }
}

Vous devez d'abord vous assurer que les valeurs du champ requestDetails correspondent à celles de la requête d'origine avant de vérifier chaque évaluation d'intégrité. Les sections suivantes décrivent chaque champ plus en détail.

Champ "Détails de la requête"

Le champ requestDetails contient des informations sur la requête, y compris les informations fournies par le développeur dans requestHash pour les requêtes standards et nonce pour les requêtes classiques.

Pour les requêtes API standards :

requestDetails: {
  // Application package name this attestation was requested for.
  // Note that this field might be spoofed in the middle of the request.
  requestPackageName: "com.package.name"
  // Request hash provided by the developer.
  requestHash: "aGVsbG8gd29scmQgdGhlcmU"
  // The timestamp in milliseconds when the integrity token
  // was prepared (computed on the server).
  timestampMillis: "1675655009345"
}

Ces valeurs doivent correspondre à celles de la requête d'origine. Par conséquent, vérifiez la partie requestDetails de la charge utile JSON en vous assurant que requestPackageName et requestHash correspondent à ce qui a été envoyé dans la requête d'origine, comme dans l'extrait de code suivant :

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("requestDetails")
val requestPackageName = requestDetails.getString("requestPackageName")
val requestHash = requestDetails.getString("requestHash")
val timestampMillis = requestDetails.getLong("timestampMillis")
val currentTimestampMillis = ...

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request
    || !requestHash.equals(expectedRequestHash)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Java

RequestDetails requestDetails =
    decodeIntegrityTokenResponse
    .getTokenPayloadExternal()
    .getRequestDetails();
String requestPackageName = requestDetails.getRequestPackageName();
String requestHash = requestDetails.getRequestHash();
long timestampMillis = requestDetails.getTimestampMillis();
long currentTimestampMillis = ...;

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request.
    || !requestHash.equals(expectedRequestHash)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Pour les requêtes API classiques :

requestDetails: {
  // Application package name this attestation was requested for.
  // Note that this field might be spoofed in the middle of the
  // request.
  requestPackageName: "com.package.name"
  // base64-encoded URL-safe no-wrap nonce provided by the developer.
  nonce: "aGVsbG8gd29scmQgdGhlcmU"
  // The timestamp in milliseconds when the request was made
  // (computed on the server).
  timestampMillis: "1617893780"
}

Ces valeurs doivent correspondre à celles de la requête d'origine. Par conséquent, vérifiez la partie requestDetails de la charge utile JSON en vous assurant que requestPackageName et nonce correspondent à ce qui a été envoyé dans la requête d'origine, comme dans l'extrait de code suivant :

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("requestDetails")
val requestPackageName = requestDetails.getString("requestPackageName")
val nonce = requestDetails.getString("nonce")
val timestampMillis = requestDetails.getLong("timestampMillis")
val currentTimestampMillis = ...

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request. See 'Generate a nonce'
        // section of the doc on how to store/compute the expected nonce.
    || !nonce.equals(expectedNonce)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("requestDetails");
String requestPackageName = requestDetails.getString("requestPackageName");
String nonce = requestDetails.getString("nonce");
long timestampMillis = requestDetails.getLong("timestampMillis");
long currentTimestampMillis = ...;

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request. See 'Generate a nonce'
        // section of the doc on how to store/compute the expected nonce.
    || !nonce.equals(expectedNonce)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Champ "Intégrité de l'application"

Le champ appIntegrity contient des informations liées au package.

appIntegrity: {
  // PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, or UNEVALUATED.
  appRecognitionVerdict: "PLAY_RECOGNIZED"
  // The package name of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  packageName: "com.package.name"
  // The sha256 digest of app certificates (base64-encoded URL-safe).
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  certificateSha256Digest: ["6a6a1474b5cbbb2b1aa57e0bc3"]
  // The version of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  versionCode: "42"
}

appRecognitionVerdict peut avoir les valeurs suivantes :

PLAY_RECOGNIZED
L'application et le certificat correspondent aux versions distribuées par Google Play.
UNRECOGNIZED_VERSION
Le nom du certificat ou du package ne correspond pas aux données dont Google Play dispose.
UNEVALUATED
L'intégrité de l'application n'a pas été examinée. Une condition requise n'a pas été respectée (l'appareil n'est pas suffisamment fiable, par exemple).

Pour vous assurer que le jeton a été généré par une application que vous avez créée, vérifiez que l'intégrité de l'application est conforme aux attentes, comme indiqué dans l'extrait de code suivant :

Kotlin

val appIntegrity = JSONObject(payload).getJSONObject("appIntegrity")
val appRecognitionVerdict = appIntegrity.getString("appRecognitionVerdict")

if (appRecognitionVerdict == "PLAY_RECOGNIZED") {
    // Looks good!
}

Java

JSONObject appIntegrity =
    new JSONObject(payload).getJSONObject("appIntegrity");
String appRecognitionVerdict =
    appIntegrity.getString("appRecognitionVerdict");

if (appRecognitionVerdict.equals("PLAY_RECOGNIZED")) {
    // Looks good!
}

Vous pouvez également vérifier manuellement le nom du package, la version et les certificats de l'application.

Champ "Intégrité de l'appareil"

Le champ deviceIntegrity peut contenir une seule valeur, deviceRecognitionVerdict, comportant un ou plusieurs libellés représentant la capacité d'un appareil à appliquer l'intégrité des applications. Si un appareil ne répond aux critères d'aucun libellé, le champ deviceIntegrity est vide.

deviceIntegrity: {
  // "MEETS_DEVICE_INTEGRITY" is one of several possible values.
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
}

Par défaut, deviceRecognitionVerdict peut contenir les éléments suivants :

MEETS_DEVICE_INTEGRITY
L'application s'exécute sur un appareil Android doté des services Google Play. L'appareil répond aux vérifications d'intégrité du système et respecte la configuration Android requise.
Vide (valeur vide)
L'application est exécutée sur un appareil présentant des signes d'attaque (hook d'API, par exemple) ou un piratage du système (mode root, par exemple), ou elle n'est pas exécutée sur un appareil physique (un émulateur n'ayant pas réussi les contrôles d'intégrité de Google Play, par exemple).

Pour vous assurer que le jeton provient d'un appareil fiable, vérifiez que deviceRecognitionVerdict est conforme aux attentes, comme indiqué dans l'extrait de code suivant :

Kotlin

val deviceIntegrity =
    JSONObject(payload).getJSONObject("deviceIntegrity")
val deviceRecognitionVerdict =
    if (deviceIntegrity.has("deviceRecognitionVerdict")) {
        deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString()
    } else {
        ""
    }

if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) {
    // Looks good!
}

Java

JSONObject deviceIntegrity =
    new JSONObject(payload).getJSONObject("deviceIntegrity");
String deviceRecognitionVerdict =
    deviceIntegrity.has("deviceRecognitionVerdict")
    ? deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString()
    : "";

if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) {
    // Looks good!
}

Si vous rencontrez des problèmes d'intégrité de votre appareil lors des tests, assurez-vous que la configuration ROM d'usine est installée (par exemple, en réinitialisant l'appareil) et que le bootloader est verrouillé. Vous pouvez également créer des tests API Play Integrity dans la Play Console.

Libellés d'appareils conditionnels

Si votre application est publiée sur Google Play Jeux pour PC, le deviceRecognitionVerdict peut également contenir le libellé suivant :

MEETS_VIRTUAL_INTEGRITY
L'application s'exécute sur un émulateur Android doté des services Google Play. L'émulateur réussit les contrôles d'intégrité du système et respecte la configuration Android requise.

Informations facultatives provenant des appareils

Si vous acceptez de recevoir des libellés supplémentaires dans l'évaluation de l'intégrité, deviceRecognitionVerdict peut avoir les libellés supplémentaires suivants :

MEETS_BASIC_INTEGRITY
L'application est exécutée sur un appareil qui réussit les contrôles d'intégrité de base du système. Cet appareil ne respecte peut-être pas la configuration Android requise et n'est peut-être pas autorisé à exécuter les services Google Play. Par exemple, il est possible que l'appareil exécute une version non reconnue d'Android, qu'il utilise un bootloader déverrouillé ou qu'il n'ait pas été certifié par le fabricant.
MEETS_STRONG_INTEGRITY
L'application est exécutée sur un appareil Android équipé des services Google Play et offre une bonne garantie de l'intégrité du système, telle qu'une preuve matérielle de l'intégrité du démarrage. L'appareil répond aux vérifications d'intégrité du système et respecte la configuration Android requise.

Un même appareil renvoie plusieurs libellés dans l'évaluation de l'intégrité de l'appareil si chacun des critères de ces libellés est rempli.

Activité récente de l'appareil (bêta)

Vous pouvez également activer l'option permettant de connaître l'activité récente de l'appareil. Celle-ci indique le nombre de fois où votre application a demandé un jeton d'intégrité sur un appareil spécifique au cours de la dernière heure. Vous pouvez utiliser l'activité récente de l'appareil pour protéger votre application contre les appareils qui enregistrent une forte activité de manière anormale, ce qui peut être le signe qu'une attaque est en cours. Vous pouvez décider du niveau de confiance attribué à chaque niveau d'activité récente de l'appareil en fonction du nombre de fois que vous estimez qu'un jeton d'intégrité devrait être demandé par votre appli installée sur un appareil type au cours de chaque heure.

Si vous acceptez de recevoir recentDeviceActivity, le champ deviceIntegrity aura deux valeurs :

deviceIntegrity: {
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
  recentDeviceActivity: {
    // "LEVEL_2" is one of several possible values.
    deviceActivityLevel: "LEVEL_2"
  }
}

Les définitions de deviceActivityLevel diffèrent selon les modes et peuvent avoir l'une des valeurs suivantes:

Niveau d'activité récent de l'appareil Requête API standard Requête API classique
LEVEL_1 (prix le plus bas) L'appli a demandé 10 jetons d'intégrité ou moins sur cet appareil au cours de la dernière heure. L'appli a demandé 5 jetons d'intégrité ou moins sur cet appareil au cours de la dernière heure.
LEVEL_2 L'appli a demandé entre 11 et 25 jetons d'intégrité sur cet appareil au cours de la dernière heure. L'appli a demandé entre 6 et 15 jetons d'intégrité sur cet appareil au cours de la dernière heure.
LEVEL_3 L'appli a demandé entre 26 et 50 jetons d'intégrité sur cet appareil au cours de la dernière heure. L'appli a demandé entre 16 et 30 jetons d'intégrité sur cet appareil au cours de la dernière heure.
LEVEL_4 (priorité la plus élevée) L'appli a demandé plus de 50 jetons d'intégrité sur cet appareil au cours de la dernière heure. L'appli a demandé plus de 30 jetons d'intégrité sur cet appareil au cours de la dernière heure.
UNEVALUATED L'activité récente de l'appareil n'a pas été évaluée.

Plusieurs raisons peuvent expliquer cette situation. Par exemple :

  • L'appareil n'est pas suffisamment fiable.
  • La version de votre application installée sur l'appareil est inconnue de Google Play.
  • L'appareil rencontre des problèmes techniques.

Champ "Détails du compte"

Le champ accountDetails contient une valeur unique, appLicensingVerdict, qui représente l'état des licences de l'application.

accountDetails: {
  // This field can be LICENSED, UNLICENSED, or UNEVALUATED.
  appLicensingVerdict: "LICENSED"
}

appLicensingVerdict peut avoir une des valeurs suivantes :

LICENSED
L'utilisateur est autorisé à accéder à l'application. En d'autres termes, il a installé ou acheté votre application sur Google Play.
UNLICENSED
L'utilisateur n'est pas autorisé à accéder à l'application. C'est par exemple le cas s'il télécharge votre application indépendamment ou sans passer par Google Play. Vous pouvez montrer la boîte de dialogue GET_LICENSED à l'utilisateur en réponse à ce problème.
UNEVALUATED

Les détails concernant la licence n'ont pas été examinés, car une condition requise n'était pas respectée.

Plusieurs raisons peuvent expliquer cette situation. Par exemple :

  • L'appareil n'est pas suffisamment fiable.
  • La version de votre application installée sur l'appareil est inconnue de Google Play.
  • L'utilisateur n'est pas connecté à Google Play.

Pour vérifier que l'utilisateur dispose des droits d'accès à votre application, vérifiez que appLicensingVerdict est conforme aux attentes, comme indiqué dans l'extrait de code suivant :

Kotlin

val accountDetails = JSONObject(payload).getJSONObject("accountDetails")
val appLicensingVerdict = accountDetails.getString("appLicensingVerdict")

if (appLicensingVerdict == "LICENSED") {
    // Looks good!
}

Java

JSONObject accountDetails =
    new JSONObject(payload).getJSONObject("accountDetails");
String appLicensingVerdict = accountDetails.getString("appLicensingVerdict");

if (appLicensingVerdict.equals("LICENSED")) {
    // Looks good!
}

Champ "Détails de l'environnement"

Si vous avez activé l'évaluation Play Protect dans la Google Play Console, la réponse de l'API inclura le champ environmentDetails. Le champ environmentDetails contient une valeur unique, playProtectVerdict, qui fournit des informations sur Google Play Protect sur l'appareil.

environmentDetails: {
  playProtectVerdict: "NO_ISSUES"
}

playProtectVerdict peut avoir une des valeurs suivantes :

NO_ISSUES
Play Protect est activé et n'a détecté aucun problème d'application sur l'appareil.
NO_DATA
Play Protect est activé, mais aucune analyse n'a encore été effectuée. L'appareil ou l'application Play Store ont peut-être été réinitialisés récemment.
POSSIBLE_RISK
Play Protect est désactivé.
MEDIUM_RISK
Play Protect est activé et a détecté des applications potentiellement dangereuses installées sur l'appareil.
HIGH_RISK
Play Protect est activé et a détecté des applications dangereuses installées sur l'appareil.
UNEVALUATED

L'évaluation Play Protect n'a pas été menée.

Plusieurs raisons peuvent expliquer cette situation. Par exemple :

  • L'appareil n'est pas suffisamment fiable.
  • Jeux uniquement : le compte utilisateur n'est pas LICENSED.

Conseils sur l'utilisation de l'évaluation Play Protect

Le serveur backend de votre application peut décider de la marche à suivre en fonction de l'évaluation basée sur votre tolérance au risque. Voici quelques suggestions et actions utilisateur possibles :

NO_ISSUES
Play Protect est activé et n'a détecté aucun problème. Aucune action n'est donc requise de la part de l'utilisateur.
POSSIBLE_RISK et NO_DATA
Lorsque vous recevez ces évaluations, demandez à l'utilisateur de vérifier que Play Protect est activé et qu'une analyse a été effectuée. NO_DATA ne doit apparaître que dans de rares cas.
MEDIUM_RISK et HIGH_RISK
En fonction de votre tolérance au risque, vous pouvez demander à l'utilisateur de lancer Play Protect et de prendre des mesures concernant les avertissements Play Protect. Si l'utilisateur ne peut pas répondre à ces exigences, vous pouvez l'empêcher d'effectuer l'action du serveur.