このページでは、返された完全性判定の結果の解釈とその対応について説明します。標準 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" }
これらの値は、元のリクエストの値と一致する必要があります。したがって、次のコード スニペットに示すように、requestPackageName
と requestHash
が元のリクエストで送信されたものと一致することを確認して、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" }
これらの値は、元のリクエストの値と一致する必要があります。したがって、次のコード スニペットに示すように、requestPackageName
と nonce
が元のリクエストで送信されたものと一致することを確認して、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">
|
アカウントの詳細フィールド
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 つを含めることができます。
appAccessRiskVerdict
と playProtectVerdict
です。
アプリアクセス リスク判定(ベータ版)
アカウント アクティビティを有効にすると、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_INSTALLED
、UNKNOWN_INSTALLED
- 対応するインストール元に一致するアプリがインストールされています。
KNOWN_CAPTURING
、UNKNOWN_CAPTURING
- 権限が有効になっている実行中のアプリがあります。 アプリの実行中に画面を表示できます。これには、確認済みの ユーザー補助サービス(デバイスで実行されている Google Play で認識)
KNOWN_CONTROLLING
、UNKNOWN_CONTROLLING
- 権限が有効になっている実行中のアプリがあります。 アプリへの入力を直接制御できるため、 アプリの入出力をキャプチャするために使用しますこれには、確認済みの ユーザー補助サービス(デバイスで実行されている Google Play で認識)
KNOWN_OVERLAYS
、UNKNOWN_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_RISK
とNO_DATA
- この判定結果を受け取ったら、Play プロテクトがオンになっていてスキャンが実行されているか確認するようユーザーに依頼します。
NO_DATA
はまれにしか表示されません。 MEDIUM_RISK
とHIGH_RISK
- リスク許容度に応じて、Play プロテクトを起動し、Play プロテクトの警告に対応するようユーザーに依頼できます。ユーザーが処理できない場合 サーバー アクションからブロックできます。