完全性判定の結果

このページでは、返された完全性判定の結果の解釈とその対応について説明します。標準 API リクエストとクラシック 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"
}

これらの値は、元のリクエストの値と一致する必要があります。したがって、次のコード スニペットに示すように、requestPackageNamerequestHash が元のリクエストで送信されたものと一致することを確認して、JSON ペイロードの requestDetails の部分を検証します。

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

クラシック 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"
}

これらの値は、元のリクエストの値と一致する必要があります。したがって、次のコード スニペットに示すように、requestPackageNamenonce が元のリクエストで送信されたものと一致することを確認して、JSON ペイロードの requestDetails の部分を検証します。

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

アプリの完全性フィールド

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
アプリの完全性は評価されませんでした。デバイスの信頼性が十分でないなど、要件が満たされていませんでした。

作成したアプリによってトークンが生成されたことを確認するには、次のコード スニペットに示すように、アプリの完全性が期待どおりであることを検証します。

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

アプリのパッケージ名、アプリのバージョン、アプリの証明書を手動で確認することもできます。

デバイスの完全性フィールド

deviceIntegrity フィールドには deviceRecognitionVerdict という値のみが含まれます。この値には、デバイスがアプリの完全性をどの程度強化できるかを表すラベルが 1 つ以上含まれています。デバイスがラベルの基準を満たしていない場合、deviceIntegrity フィールドは空になります。

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

デフォルトでは、deviceRecognitionVerdict に含めることができる値は以下のとおりです。

MEETS_DEVICE_INTEGRITY
アプリは Google Play 開発者サービスを備えた Android デバイスで実行されています。このデバイスはシステム完全性チェックに合格し、Android の互換性要件を満たしています。
空(空の値)
アプリは、API フックなどの攻撃や root 権限取得などのシステム侵害の兆候があるデバイス、または Google Play の完全性チェックに合格していない仮想デバイス(エミュレータなど)で動作しています。

トークンが信頼できるデバイスから生成されたことを確認するには、次のコード スニペットに示すように、deviceRecognitionVerdict が期待どおりであることを検証します。

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

テストデバイスがデバイスの完全性要件を満たさない問題が発生した場合は、工場出荷時の ROM がインストールされていることと、ブートローダーがロックされていることを確認してください(前者はデバイスをリセットするなどの方法で確認できます)。また、Google Play Console で Play Integrity API テストを作成することもできます。

条件付きのデバイスラベル

アプリを PC 版 Google Play Games にリリースする場合は、deviceRecognitionVerdict に次のラベルを付けることもできます。

MEETS_VIRTUAL_INTEGRITY
アプリは Google Play 開発者サービスを備えた Android エミュレータで実行されています。このエミュレータはシステム完全性チェックに合格し、Android の互換性に関する主要な要件を満たしています。

オプションのデバイス情報

完全性判定の結果で追加ラベルを受け取ることをオプトインすると、deviceRecognitionVerdict に以下のラベルを追加できます。

MEETS_BASIC_INTEGRITY
アプリは、基本的なシステム完全性チェックに合格したデバイスで動作しています。このデバイスは Android の互換性要件を満たしておらず、Google Play 開発者サービスの実行を承認されていない可能性があります。たとえば、認識されていないバージョンの Android を搭載している、ブートローダーがロック解除されている、メーカーによる認定を受けていない、などの場合があります。
MEETS_STRONG_INTEGRITY
アプリは、Google Play 開発者サービスを搭載した Android デバイスで動作しており、システム完全性の強力な保証(ハードウェア格納型のブート完全性保証など)が存在します。このデバイスはシステム完全性チェックに合格し、Android の互換性要件を満たしています。

ラベルのそれぞれの条件が満たされている場合、1 つのデバイスがデバイスの完全性判定の結果で複数のデバイスラベルを返します。

最近のデバイスのアクティビティ

最近のデバイスのアクティビティをオプトインすることもできます。これにより、過去 1 時間にアプリが特定のデバイスで完全性トークンをリクエストした回数を確認できます。最近のデバイスのアクティビティを使用することで、予期せぬ不審なアクティビティを過剰に行っている(アクティブな攻撃を示している可能性がある)デバイスからアプリを保護できます。一般的なデバイスにインストールされたアプリが 1 時間ごとに完全性トークンをリクエストする回数の予測に基づいて、最近のデバイスのアクティビティの信頼レベルを決定できます。

recentDeviceActivity を受け取ることをオプトインした場合、deviceIntegrity フィールドには以下の 2 つの値が含まれるようになります。

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

deviceActivityLevel の定義はモードによって異なり、必要に応じて 次のいずれかの値になります。

最近のデバイスのアクティビティ レベル このデバイスでの標準 API 完全性トークン リクエスト 過去 1 時間(アプリごと) このデバイスでのクラシック API 完全性トークン リクエスト 過去 1 時間(アプリごと)
LEVEL_1(最低) 10 本以下 5 以下
LEVEL_2 11 ~ 25 6 ~ 10
LEVEL_3 26 ~ 50 11 ~ 15
LEVEL_4(最高) 50 以上 16 以上
UNEVALUATED 最近のデバイスのアクティビティは評価されませんでした。この問題は、 理由: <ph type="x-smartling-placeholder">
    </ph>
  • デバイスの信頼性が十分でない。
  • デバイスにインストールされているアプリのバージョンが Google に認識されていない プレイする。
  • デバイスで技術的な問題が起きている。

アカウントの詳細フィールド

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 が想定どおりであることを確認します。

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

環境の詳細フィールド

また、環境に関する追加のシグナルを有効にすることもできます。アプリのアクセス権 他の実行中のアプリがある場合に 画面のキャプチャ、オーバーレイの表示、デバイスの操作を行えます。Play プロテクト 判定には、Google Play プロテクトがデバイスで有効になっているかどうかと、 既知のマルウェアが検出されたことを示しています。

アプリアクセス リスクの判定結果または Play プロテクトの判定結果に同意している場合 API レスポンスに environmentDetails フィールド。environmentDetails フィールドには 2 つを含めることができます。 appAccessRiskVerdictplayProtectVerdict です。

アプリアクセス リスク判定(ベータ版)

アカウント アクティビティを有効にすると、Play Integrity API ペイロードenvironmentDetails フィールドに、新しいアプリアクセス リスク判定結果が含まれます。

{
  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 は 1 つ以上のレスポンスに置き換えます。これらの回答は 検出されたアプリのインストール元に応じて、次の 2 つのグループがあります。

  • Play アプリまたはシステムアプリ: Google Play によってインストールされたアプリまたはプリロードされているアプリ デバイスのシステム パーティション( FLAG_SYSTEM)。 このようなアプリのレスポンスには、先頭に KNOWN_ が付きます。

  • その他のアプリ: Google Play からインストールされていないアプリ。これに以下は含まれません。 デバイスのメーカーによってシステム パーティションにプリロードされたアプリ。回答 プレフィックスは UNKNOWN_ になります。

で確認できます。

次のようなレスポンスが返されます。

KNOWN_INSTALLEDUNKNOWN_INSTALLED
対応するインストール元に一致するアプリがインストールされています。
KNOWN_CAPTURINGUNKNOWN_CAPTURING
権限が有効になっている実行中のアプリがあります。 アプリの実行中に画面を表示できます。これには、確認済みの ユーザー補助サービス(デバイスで実行されている Google Play で認識)
KNOWN_CONTROLLINGUNKNOWN_CONTROLLING
権限が有効になっている実行中のアプリがあります。 アプリへの入力を直接制御できるため、 アプリの入出力をキャプチャするために使用しますこれには、確認済みの ユーザー補助サービス(デバイスで実行されている Google Play で認識)
KNOWN_OVERLAYSUNKNOWN_OVERLAYS
権限が有効になっている実行中のアプリがあります。 オーバーレイをアプリ上に配置できます。検証済みのアクセシビリティは除外されます サービスとして認識されています
EMPTY(空の値)

要件が満たされていない場合、アプリアクセス リスクは評価されません。イン この場合、appAccessRiskVerdict フィールドは空です。この問題は次の場合に発生する可能性があります。 次のようないくつかの理由があります。

  • デバイスの信頼性が十分でない。
  • デバイスのフォーム ファクタがスマートフォン、タブレット、折りたたみ式ではない。
  • デバイスに Android 6(API レベル 23)以降が搭載されていない。
  • デバイスにインストールされているアプリのバージョンが Google Play に認識されていない。
  • デバイス上の Google Play ストアのバージョンが古い。
  • ゲームのみ: ユーザー アカウントにゲームの Play ライセンスがない。
  • 標準リクエストは verdictOptOut パラメータとともに使用されました。
  • Play Integrity API ライブラリのバージョンで標準リクエストが使用されました では、標準リクエストのアプリアクセス リスクにまだ対応していません。

アプリアクセス リスクでは、検証済みのユーザー補助サービスは自動的に除外されます。 Google Play のユーザー補助機能に関する高度な審査を通過している( デバイス上の任意のアプリストア)で 管理できます「除外済み」検証済みのアクセシビリティは デバイス上で実行されているサービスは、キャプチャ、制御、 アプリアクセス リスクの判定結果でレスポンスをオーバーレイします。Google Cloud の ユーザー補助アプリのユーザー補助審査を再生し、Google で公開しましょう 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: {} 要件が満たされていないため、アプリアクセス リスクは評価されませんでした(たとえば、デバイスの信頼性が十分ではなかった場合など)。

リスクレベルに応じて、続行できる判定結果の組み合わせと、対処する判定結果を決定できます。「 次のコード スニペットは、存在しないことを確認する例を 画面をキャプチャしたり、アプリを操作したりする可能性がある実行中のアプリ:

Kotlin

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

Java

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 プロテクトの判定結果

有効にすると、Play Integrity API の environmentDetails フィールド ペイロードには、 Play プロテクトの判定結果:

environmentDetails: {
  playProtectVerdict: "NO_ISSUES"
}

playProtectVerdict の値は次のいずれかになります。

NO_ISSUES
Play プロテクトはオンになっていますが、デバイスにおいてアプリの問題は検知されませんでした。
NO_DATA
Play プロテクトはオンになっていますが、まだスキャンが実行されていません。デバイスまたは Play ストア アプリが最近リセットされた可能性があります。
POSSIBLE_RISK
Play プロテクトがオフになっています。
MEDIUM_RISK
Play プロテクトがオンになっており、有害な可能性があるアプリがデバイスにインストールされていることが検知されました。
HIGH_RISK
Play プロテクトがオンになっており、危険なアプリがデバイスにインストールされていることが検知されました。
UNEVALUATED

Play プロテクトの判定結果は評価されませんでした。

次のような原因が考えられます。

  • デバイスの信頼性が十分でない。
  • ゲームのみ: ユーザー アカウントにゲームの Play ライセンスがない。

Play プロテクトの判定結果の使用に関するガイダンス

アプリのバックエンド サーバーは、リスク許容度に基づいて、判定結果に基づく処理方法を決定できます。推奨される使い方と、考えられるユーザーの操作には次のようなものがあります。

NO_ISSUES
Play プロテクトがオンになっていて、問題は検出されていないため、ユーザーの操作は必要ありません。
POSSIBLE_RISKNO_DATA
この判定結果を受け取ったら、Play プロテクトがオンになっていてスキャンが実行されているか確認するようユーザーに依頼します。NO_DATA はまれにしか表示されません。
MEDIUM_RISKHIGH_RISK
リスク許容度に応じて、Play プロテクトを起動し、Play プロテクトの警告に対応するようユーザーに依頼できます。ユーザーが処理できない場合 サーバー アクションからブロックできます。