Вердикты о честности

На этой странице описывается, как интерпретировать полученный вердикт целостности и работать с ним. Независимо от того, делаете ли вы стандартный или классический запрос API, вердикт целостности возвращается в том же формате с похожим содержимым. Вердикт целостности передает информацию о действительности устройств, приложений и учетных записей. Сервер вашего приложения может использовать полученные полезные данные в виде расшифрованного и проверенного вердикта, чтобы определить, как лучше всего выполнить определенное действие или запрос в вашем приложении.

Возвращаемый формат вердикта целостности

Полезная нагрузка представляет собой обычный текст JSON и содержит сигналы целостности наряду с информацией, предоставленной разработчиком.

Общая структура полезной нагрузки выглядит следующим образом:

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

Прежде чем проверять каждый вердикт целостности, необходимо сначала убедиться, что значения в поле requestDetails совпадают со значениями исходного запроса. В следующих разделах каждое поле описывается более подробно.

Поле сведений о запросе

Поле requestDetails содержит информацию о запросе, включая предоставленную разработчиком информацию в requestHash для стандартных запросов и nonce для классических запросов.

Для стандартных запросов API:

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 requested.
  timestampMillis: "1675655009345"
}

Эти значения должны соответствовать значениям исходного запроса. Поэтому проверьте часть requestDetails полезных данных JSON, убедившись, что requestPackageName и requestHash соответствуют тому, что было отправлено в исходном запросе, как показано в следующем фрагменте кода:

Котлин

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

Джава

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

Для классических запросов API:

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

Эти значения должны соответствовать значениям исходного запроса. Поэтому проверьте часть requestDetails полезных данных JSON, убедившись, что requestPackageName и nonce соответствуют тому, что было отправлено в исходном запросе, как показано в следующем фрагменте кода:

Котлин

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

Джава

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

Поле целостности приложения

Поле appIntegrity содержит информацию, связанную с пакетом.

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 может иметь следующие значения:

PLAY_RECOGNIZED
Приложение и сертификат соответствуют версиям, распространяемым Google Play.
UNRECOGNIZED_VERSION
Название сертификата или пакета не соответствует записям Google Play.
UNEVALUATED
Целостность приложения не оценивалась. Было пропущено необходимое требование, например, устройство не заслуживало достаточного доверия.

Чтобы убедиться, что токен был создан созданным вами приложением, убедитесь, что целостность приложения соответствует ожиданиям, как показано в следующем фрагменте кода:

Котлин

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

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

Джава

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

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

Вы также можете проверить имя пакета приложения, версию приложения и сертификаты приложения вручную.

Поле целостности устройства

Поле deviceIntegrity может содержать одно значение, deviceRecognitionVerdict , которое имеет одну или несколько меток, показывающих, насколько хорошо устройство может обеспечить целостность приложения. Если устройство не соответствует критериям какой-либо метки, поле deviceIntegrity остается пустым.

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

По умолчанию deviceRecognitionVerdict может содержать следующее:

MEETS_DEVICE_INTEGRITY
Приложение работает на устройстве под управлением Android с сервисами Google Play. Устройство проходит проверку целостности системы и соответствует требованиям совместимости с Android.
Пусто (пустое значение)
Приложение работает на устройстве, имеющем признаки атаки (например, перехват API) или взлома системы (например, рутирование), или приложение не работает на физическом устройстве (например, эмулятор, который не проходит проверку целостности Google Play). чеки).

Чтобы убедиться, что токен получен от надежного устройства, убедитесь, что значение deviceRecognitionVerdict соответствует ожидаемому, как показано в следующем фрагменте кода:

Котлин

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

Джава

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

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

Если у вас возникли проблемы с целостностью устройства для тестирования, убедитесь, что установлено заводское ПЗУ (например, путем перезагрузки устройства) и что загрузчик заблокирован. Вы также можете создавать тесты API целостности Play в своей консоли Play .

Условные метки устройств

Если ваше приложение выпускается в Google Play Games для ПК , deviceRecognitionVerdict также может содержать следующую метку:

MEETS_VIRTUAL_INTEGRITY
Приложение работает на эмуляторе Android с сервисами Google Play. Эмулятор проходит проверку целостности системы и соответствует основным требованиям совместимости Android.

Дополнительная информация об устройстве

Если вы согласитесь получать дополнительные метки в вердикте целостности, deviceRecognitionVerdict может содержать следующие дополнительные метки:

MEETS_BASIC_INTEGRITY
Приложение работает на устройстве, которое проходит базовую проверку целостности системы. Устройство может не соответствовать требованиям совместимости Android и не быть одобрено для запуска сервисов Google Play. Например, устройство может работать под управлением неизвестной версии Android, иметь разблокированный загрузчик или не быть сертифицировано производителем.
MEETS_STRONG_INTEGRITY
Приложение работает на устройстве под управлением Android с сервисами Google Play и имеет надежную гарантию целостности системы, например аппаратное подтверждение целостности загрузки. Устройство проходит проверку целостности системы и соответствует требованиям совместимости с Android.

Одно устройство вернет несколько меток устройства в вердикте целостности устройства, если каждый из критериев метки соблюден.

Недавняя активность устройства

Вы также можете подписаться на недавнюю активность устройства, которая сообщает вам, сколько раз ваше приложение запрашивало токен целостности на определенном устройстве за последний час. Вы можете использовать недавнюю активность устройств, чтобы защитить свое приложение от неожиданных гиперактивных устройств, которые могут указывать на активную атаку. Вы можете решить, насколько доверять каждому недавнему уровню активности устройства, исходя из того, сколько раз ваше приложение, установленное на типичном устройстве, будет запрашивать токен целостности каждый час.

Если вы дадите согласие на получение recentDeviceActivity поле deviceIntegrity будет иметь два значения:

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

Определения deviceActivityLevel различаются в зависимости от режима и могут иметь одно из следующих значений:

Недавний уровень активности устройства Стандартные запросы токена целостности API на этом устройстве за последний час для каждого приложения Запросы токена целостности классического API на этом устройстве за последний час для каждого приложения
LEVEL_1 (самый низкий) 10 или меньше 5 или меньше
LEVEL_2 Между 11 и 25 Между 6 и 10
LEVEL_3 От 26 до 50 Между 11 и 15
LEVEL_4 (самый высокий) Более 50 Более 15
UNEVALUATED Недавняя активность устройства не оценивалась. Это может произойти потому, что:
  • Устройство не заслуживает доверия.
  • Версия вашего приложения, установленного на устройстве, неизвестна Google Play.
  • Технические проблемы с устройством.

Поле сведений об учетной записи

Поле accountDetails содержит одно значение appLicensingVerdict , которое представляет статус лицензирования приложения Google Play для учетной записи пользователя, вошедшего в систему на устройстве. Если у учетной записи пользователя есть лицензия Play на приложение, это означает, что он скачал или купил его в Google Play.

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

appLicensingVerdict может иметь одно из следующих значений:

LICENSED
У пользователя есть право на использование приложения. Другими словами, пользователь установил или купил ваше приложение в Google Play.
UNLICENSED
У пользователя нет прав на использование приложения. Это происходит, например, когда пользователь загружает ваше приложение или не скачивает его из Google Play. Чтобы исправить эту проблему, вы можете показать пользователям диалоговое окно GET_LICENSED .
UNEVALUATED

Детали лицензирования не оценивались, поскольку было пропущено необходимое требование.

Это может произойти по нескольким причинам, включая следующие:

  • Устройство не заслуживает доверия.
  • Версия вашего приложения, установленного на устройстве, неизвестна Google Play.
  • Пользователь не авторизован в Google Play.

Чтобы проверить, есть ли у пользователя право доступа к вашему приложению, убедитесь, что appLicensingVerdict соответствует ожидаемому, как показано в следующем фрагменте кода:

Котлин

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

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

Джава

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

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

Поле сведений о среде

Вы также можете подписаться на дополнительные сигналы об окружающей среде. Риск доступа к приложению сообщает вашему приложению, есть ли запущены другие приложения, которые можно использовать для захвата экрана, отображения наложений или управления устройством. В вердикте Play Protect указывается, включена ли Google Play Protect на устройстве и обнаружено ли на нем известное вредоносное ПО.

Если вы согласились на вердикт «Риск доступа к приложениям» или вердикт «Play Protect» в консоли Google Play, то ваш ответ API будет включать поле environmentDetails . Поле environmentDetails может содержать два значения: appAccessRiskVerdict и playProtectVerdict .

Вердикт о риске доступа к приложению (бета)

После включения поле environmentDetails в полезных данных Play Integrity API будет содержать новый вердикт о риске доступа к приложению.

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
  environmentDetails: {
      appAccessRiskVerdict: {
          // This field contains one or more responses, for example the following.
          appsDetected: ["KNOWN_INSTALLED", "UNKNOWN_INSTALLED", "UNKNOWN_CAPTURING"]
      }
 }
}

Если риск доступа к приложению был оценен, appAccessRiskVerdict содержит поле appsDetected с одним или несколькими ответами. Эти ответы попадают в одну из следующих двух групп в зависимости от источника установки обнаруженных приложений:

  • Play или системные приложения : приложения, которые установлены Google Play или предварительно загружены производителем устройства в системный раздел устройства (обозначается FLAG_SYSTEM ). Ответы для таких приложений имеют префикс KNOWN_ .

  • Другие приложения : приложения, которые не установлены из Google Play. Сюда не входят приложения, предварительно загруженные в системный раздел производителем устройства. Ответы для таких приложений имеют префикс UNKNOWN_ .

Могут быть возвращены следующие ответы:

KNOWN_INSTALLED , UNKNOWN_INSTALLED
Установлены приложения, соответствующие соответствующему источнику установки.
KNOWN_CAPTURING , UNKNOWN_CAPTURING
Существуют запущенные приложения, у которых включены разрешения, которые можно использовать для просмотра экрана во время работы вашего приложения. Это исключает любые проверенные службы специальных возможностей, известные Google Play, работающие на устройстве.
KNOWN_CONTROLLING , UNKNOWN_CONTROLLING
Существуют запущенные приложения, у которых включены разрешения, которые можно использовать для управления устройством и непосредственного управления входными данными в ваше приложение, а также для захвата входных и выходных данных вашего приложения. Это исключает любые проверенные службы специальных возможностей, известные Google Play, работающие на устройстве.
KNOWN_OVERLAYS , UNKNOWN_OVERLAYS
Существуют запущенные приложения, у которых включены разрешения, которые можно использовать для отображения наложений в вашем приложении. Это исключает любые проверенные службы специальных возможностей, известные Google Play, работающие на устройстве.
ПУСТОЙ (пустое значение)

Риск доступа к приложению не оценивается, если необходимое требование было пропущено. В этом случае поле appAccessRiskVerdict пусто. Это может произойти по нескольким причинам, включая следующие:

  • Устройство не заслуживает доверия.
  • Форм-фактор устройства не является телефоном, планшетом или складным устройством.
  • Устройство не работает под управлением Android 6 (уровень API 23) или выше.
  • Версия вашего приложения, установленного на устройстве, неизвестна Google Play.
  • Версия Google Play Store на устройстве устарела.
  • Только игры : у учетной записи пользователя нет лицензии Play на игру.
  • Использовался стандартный запрос с параметром verdictOptOut .
  • Стандартный запрос использовался с версией библиотеки Play Integrity API, которая еще не поддерживает риск доступа к приложениям для стандартных запросов.

Риск доступа к приложениям автоматически исключает проверенные службы специальных возможностей, прошедшие расширенную проверку доступности Google Play (установленные любым магазином приложений на устройстве). «Исключено» означает, что проверенные службы специальных возможностей, работающие на устройстве, не будут возвращать ответ о захвате, контроле или наложении в вердикте о риске доступа к приложению. Чтобы запросить расширенную проверку доступности вашего приложения в Google Play, опубликуйте его в Google Play и убедитесь, что в манифесте вашего приложения для флага isAccessibilityTool установлено значение true, или запросите проверку .

В следующей таблице приведены некоторые примеры вердиктов и их значение (в этой таблице не перечислены все возможные результаты):

Пример ответа на вердикт о риске доступа к приложению Интерпретация
appsDetected:
["KNOWN_INSTALLED"]
Установлены только приложения, которые распознаются Google Play или предварительно загружены в системный раздел производителем устройства.
Нет запущенных приложений, которые могли бы привести к захвату, контролю или наложению вердиктов.
appsDetected:
["KNOWN_INSTALLED",
"UNKNOWN_INSTALLED",
"UNKNOWN_CAPTURING"]
Есть приложения, установленные из Google Play или предварительно загруженные в системный раздел производителем устройства.
Есть другие запущенные приложения, и у них включены разрешения, которые можно использовать для просмотра экрана или захвата других входных и выходных данных.
appsDetected:
["KNOWN_INSTALLED",
"KNOWN_CAPTURING",
"UNKNOWN_INSTALLED",
"UNKNOWN_CONTROLLING"]
Есть Play или запущенная система, у которой включены разрешения, которые можно использовать для просмотра экрана или захвата других входных и выходных данных.
Существуют также другие запущенные приложения, у которых включены разрешения, которые можно использовать для управления устройством и непосредственного управления входными данными в ваше приложение.
appAccessRiskVerdict: {} Риск доступа к приложению не оценивается, поскольку было пропущено необходимое требование. Например, устройство не было достаточно надежным.

В зависимости от уровня риска вы можете решить, какая комбинация вердиктов приемлема для продолжения и по каким вердиктам вы хотите принять меры. Следующий фрагмент кода иллюстрирует пример проверки отсутствия запущенных приложений, которые могли бы захватывать экран или управлять вашим приложением:

Котлин

val environmentDetails =
    JSONObject(payload).getJSONObject("environmentDetails")
val appAccessRiskVerdict =
    environmentDetails.getJSONObject("appAccessRiskVerdict")

if (appAccessRiskVerdict.has("appsDetected")) {
    val appsDetected = appAccessRiskVerdict.getJSONArray("appsDetected").toString()
    if (!appsDetected.contains("CAPTURING") && !appsDetected.contains("CONTROLLING")) {
        // Looks good!
    }
}

Джава

JSONObject environmentDetails =
    new JSONObject(payload).getJSONObject("environmentDetails");
JSONObject appAccessRiskVerdict =
    environmentDetails.getJSONObject("appAccessRiskVerdict");

if (appAccessRiskVerdict.has("appsDetected")) {
    String appsDetected = appAccessRiskVerdict.getJSONArray("appsDetected").toString()
    if (!appsDetected.contains("CAPTURING") && !appsDetected.contains("CONTROLLING")) {
        // Looks good!
    }
}
Устранение вердиктов о рисках доступа к приложениям

В зависимости от уровня риска вы можете решить, какие вердикты о риске доступа к приложению вы хотите принять, прежде чем позволить пользователю выполнить запрос или действие. Существуют дополнительные подсказки Google Play, которые вы можете показать пользователю после проверки вердикта о риске доступа к приложению. Вы можете указать CLOSE_UNKNOWN_ACCESS_RISK , чтобы попросить пользователя закрыть неизвестные приложения, что приведет к вынесению вердикта о риске доступа к приложению, или вы можете показать CLOSE_ALL_ACCESS_RISK , чтобы попросить пользователя закрыть все приложения (известные и неизвестные), вызывающие вердикт о риске доступа к приложению.

Вердикт Play Protect

После включения поле environmentDetails в полезных данных Play Integrity API будет содержать вердикт Play Protect:

environmentDetails: {
  playProtectVerdict: "NO_ISSUES"
}

playProtectVerdict может иметь одно из следующих значений:

NO_ISSUES
Play Protect включен, и на устройстве не обнаружено проблем с приложениями.
NO_DATA
Play Protect включен, но сканирование еще не выполнено. Возможно, устройство или приложение Play Store недавно были сброшены.
POSSIBLE_RISK
Play Protect отключен.
MEDIUM_RISK
Play Protect включен и обнаружил, что на устройстве установлены потенциально опасные приложения.
HIGH_RISK
Play Protect включен и обнаружил, что на устройстве установлены опасные приложения.
UNEVALUATED

Вердикт Play Protect не был оценен.

Это может произойти по нескольким причинам, включая следующие:

  • Устройство не заслуживает доверия.
  • Только игры : у учетной записи пользователя нет лицензии Play на игру.

Руководство по использованию вердикта Play Protect

Внутренний сервер вашего приложения может решить, как действовать, основываясь на вердикте, основанном на вашей толерантности к риску. Вот несколько предложений и возможных действий пользователя:

NO_ISSUES
Play Protect включен и не обнаружил никаких проблем, поэтому никаких действий со стороны пользователя не требуется.
POSSIBLE_RISK и NO_DATA
При получении этих вердиктов попросите пользователя убедиться, что Play Protect включен и выполнил сканирование. NO_DATA должен появляться только в редких случаях.
MEDIUM_RISK и HIGH_RISK
В зависимости от вашей толерантности к риску вы можете попросить пользователя запустить Play Protect и принять меры по предупреждениям Play Protect. Если пользователь не может выполнить эти требования, вы можете заблокировать его от действий сервера.