Verdict integritas

Halaman ini menjelaskan cara menafsirkan dan menggunakan verdict integritas yang ditampilkan. Baik Anda membuat permintaan API standar maupun klasik, verdict integritas ditampilkan dalam format yang sama dengan konten serupa. Verdict integritas menyampaikan informasi tentang validitas perangkat, aplikasi, dan akun. Server aplikasi Anda dapat menggunakan payload yang dihasilkan dalam verdict yang telah didekripsi dan diverifikasi guna menentukan cara terbaik untuk melanjutkan dengan tindakan atau permintaan tertentu di aplikasi Anda.

Format verdict integritas yang ditampilkan

Payload berupa JSON teks biasa dan berisi sinyal integritas bersama informasi yang disediakan developer.

Berikut adalah struktur payload umum:

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

Anda harus memastikan terlebih dahulu bahwa nilai di kolom requestDetails cocok dengan nilai dari permintaan asli sebelum memeriksa setiap verdict integritas. Bagian berikut menjelaskan setiap kolom secara lebih mendetail.

Kolom detail permintaan

Kolom requestDetails berisi informasi tentang permintaan, termasuk informasi yang disediakan developer di requestHash untuk permintaan standar dan nonce untuk permintaan klasik.

Untuk permintaan API standar:

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

Nilai ini harus cocok dengan permintaan asal. Oleh karena itu, verifikasi bagian requestDetails payload JSON dengan memastikan bahwa requestPackageName dan requestHash cocok dengan yang dikirim dalam permintaan asli, seperti yang ditunjukkan dalam cuplikan kode:

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

Untuk permintaan API klasik:

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

Nilai ini harus cocok dengan permintaan asal. Oleh karena itu, verifikasi bagian requestDetails payload JSON dengan memastikan bahwa requestPackageName dan nonce cocok dengan yang dikirim dalam permintaan asli, seperti yang ditunjukkan dalam cuplikan kode:

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

Kolom integritas aplikasi

Kolom appIntegrity berisi informasi terkait paket.

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 dapat memiliki nilai berikut:

PLAY_RECOGNIZED
Aplikasi dan sertifikat cocok dengan versi yang didistribusikan oleh Google Play.
UNRECOGNIZED_VERSION
Nama paket atau sertifikat tidak cocok dengan data Google Play.
UNEVALUATED
Integritas aplikasi tidak dievaluasi. Persyaratan yang diperlukan tidak terpenuhi, seperti perangkat tidak cukup tepercaya.

Untuk memastikan bahwa token dibuat oleh aplikasi yang dibuat oleh Anda, pastikan integritas aplikasi seperti yang diharapkan, seperti yang ditampilkan dalam cuplikan kode berikut:

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

Anda juga dapat memeriksa nama paket aplikasi, versi aplikasi, dan sertifikat aplikasi secara manual.

Kolom integritas perangkat

Kolom deviceIntegrity dapat berisi satu nilai, deviceRecognitionVerdict, yang memiliki satu atau beberapa label yang merepresentasikan seberapa baik perangkat dapat menerapkan integritas aplikasi. Jika perangkat tidak memenuhi kriteria label apa pun, kolom deviceIntegrity akan kosong.

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

Secara default, deviceRecognitionVerdict dapat berisi hal berikut:

MEETS_DEVICE_INTEGRITY
Aplikasi berjalan di perangkat yang didukung Android dengan layanan Google Play. Perangkat lulus pemeriksaan integritas sistem dan memenuhi persyaratan kompatibilitas Android.
Kosong (nilai kosong)
Aplikasi berjalan pada perangkat yang memiliki tanda-tanda serangan (seperti hooking API) atau penyusupan sistem (seperti di-root), atau aplikasi tidak berjalan pada perangkat fisik (seperti emulator yang tidak lulus pemeriksaan integritas Google Play).

Untuk memastikan bahwa token berasal dari perangkat tepercaya, pastikan deviceRecognitionVerdict sesuai dengan yang diharapkan, seperti ditunjukkan dalam cuplikan kode berikut:

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

Jika Anda mengalami masalah dengan pengujian perangkat memenuhi integritas perangkat, pastikan ROM pabrik sudah diinstal (misalnya, dengan mereset perangkat) dan bootloader terkunci. Anda juga dapat membuat pengujian Play Integrity API di Konsol Play.

Label perangkat bersyarat

Jika aplikasi Anda dirilis ke Google Play Game untuk PC, deviceRecognitionVerdict juga dapat berisi label berikut:

MEETS_VIRTUAL_INTEGRITY
Aplikasi berjalan di emulator yang didukung Android dengan layanan Google Play. Emulator lulus pemeriksaan integritas sistem dan memenuhi persyaratan kompatibilitas inti Android.

Informasi perangkat opsional

Jika Anda memilih untuk menerima label tambahan dalam verdict integritas, deviceRecognitionVerdict dapat memiliki label tambahan berikut:

MEETS_BASIC_INTEGRITY
Aplikasi berjalan di perangkat yang telah lulus pemeriksaan integritas sistem dasar. Perangkat mungkin tidak memenuhi persyaratan kompatibilitas Android dan mungkin tidak disetujui untuk menjalankan layanan Google Play. Misalnya, perangkat mungkin menjalankan versi Android yang tidak dikenal, mungkin memiliki bootloader yang tidak terkunci, atau mungkin belum disertifikasi oleh produsen.
MEETS_STRONG_INTEGRITY
Aplikasi tersebut berjalan di perangkat yang didukung Android dengan layanan Google Play dan memiliki jaminan kuat atas integritas sistem seperti bukti integritas booting yang didukung hardware. Perangkat lulus pemeriksaan integritas sistem dan memenuhi persyaratan kompatibilitas Android.

Satu perangkat akan menampilkan beberapa label perangkat dalam verdict integritas perangkat jika setiap kriteria label terpenuhi.

Aktivitas perangkat terbaru (beta)

Anda juga dapat ikut serta dalam aktivitas perangkat terbaru, yang memberi tahu berapa kali aplikasi Anda meminta token integritas di perangkat tertentu dalam satu jam terakhir. Anda dapat menggunakan aktivitas perangkat terbaru untuk melindungi aplikasi dari perangkat hiperaktif yang tidak terduga yang mungkin merupakan indikasi serangan aktif. Anda dapat menentukan seberapa besar tingkat kepercayaan terhadap setiap level aktivitas perangkat terbaru berdasarkan perkiraan jumlah aplikasi Anda diinstal di perangkat standar untuk meminta token integritas setiap jamnya.

Jika Anda memilih untuk menerima recentDeviceActivity, kolom deviceIntegrity akan memiliki dua nilai:

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

deviceActivityLevel dapat memiliki salah satu nilai berikut:

LEVEL_1
Level terendah: Aplikasi meminta 10 token integritas atau kurang di perangkat ini dalam satu jam terakhir.
LEVEL_2
Aplikasi meminta antara 11 dan 25 token integritas di perangkat ini dalam satu jam terakhir.
LEVEL_3
Aplikasi meminta antara 26 dan 50 token integritas di perangkat ini dalam satu jam terakhir.
LEVEL_4
Level tertinggi: Aplikasi meminta lebih dari 50 token integritas di perangkat ini dalam satu jam terakhir.
UNEVALUATED

Level aktivitas perangkat terbaru tidak dievaluasi. Hal ini dapat terjadi karena beberapa alasan, termasuk:

  • Perangkat tidak cukup tepercaya.
  • Versi aplikasi yang diinstal di perangkat tidak dikenal oleh Google Play.
  • Masalah teknis pada perangkat.

Definisi level hanya perkiraan dan dapat berubah sewaktu-waktu.

Kolom detail akun

Kolom accountDetails berisi satu nilai, appLicensingVerdict, yang menunjukkan status pemberian lisensi aplikasi.

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

appLicensingVerdict dapat memiliki salah satu nilai berikut:

LICENSED
Pengguna memiliki hak aplikasi. Dengan kata lain, pengguna menginstal atau membeli aplikasi Anda di Google Play.
UNLICENSED
Pengguna tidak memiliki hak aplikasi. Hal ini terjadi saat, misalnya, pengguna melakukan sideload pada aplikasi Anda atau tidak menginstalnya dari Google Play. Anda dapat menampilkan dialog GET_LicenseD kepada pengguna untuk memperbaiki masalah ini.
UNEVALUATED

Detail pemberian lisensi tidak dievaluasi karena persyaratan yang diperlukan tidak terpenuhi.

Hal ini dapat terjadi karena beberapa alasan, termasuk:

  • Perangkat tidak cukup tepercaya.
  • Versi aplikasi yang diinstal di perangkat tidak dikenal oleh Google Play.
  • Pengguna tidak login ke Google Play.

Untuk memeriksa apakah pengguna memiliki hak aplikasi untuk aplikasi Anda, verifikasi bahwa appLicensingVerdict sudah sesuai harapan, seperti yang ditunjukkan dalam cuplikan kode berikut:

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

Kolom detail lingkungan

Jika Anda telah memilih ikut serta dalam verdict Play Protect di Konsol Google Play, respons API Anda akan menyertakan kolom environmentDetails. Kolom environmentDetails berisi satu nilai, playProtectVerdict, yang memberikan informasi tentang Google Play Protect di perangkat.

environmentDetails: {
  playProtectVerdict: "NO_ISSUES"
}

playProtectVerdict dapat memiliki salah satu nilai berikut:

NO_ISSUES
Play Protect diaktifkan dan tidak menemukan masalah aplikasi apa pun di perangkat.
NO_DATA
Play Protect diaktifkan, tetapi belum ada pemindaian yang dilakukan. Perangkat atau aplikasi Play Store mungkin baru saja direset.
POSSIBLE_RISK
Play Protect dinonaktifkan.
MEDIUM_RISK
Play Protect diaktifkan dan telah menemukan aplikasi yang berpotensi membahayakan diinstal di perangkat.
HIGH_RISK
Play Protect diaktifkan dan telah menemukan aplikasi berbahaya diinstal di perangkat.
UNEVALUATED

Verdict Play Protect tidak dievaluasi.

Hal ini dapat terjadi karena beberapa alasan, termasuk:

  • Perangkat tidak cukup tepercaya.
  • Khusus game: Akun pengguna bukan LICENSED.

Panduan cara menggunakan verdict Play Protect

Server backend aplikasi Anda dapat menentukan cara bertindak berdasarkan verdict berdasarkan toleransi risiko Anda. Berikut adalah beberapa saran dan tindakan yang dapat dilakukan pengguna:

NO_ISSUES
Play Protect diaktifkan dan belum menemukan masalah apa pun sehingga tidak ada tindakan yang perlu dilakukan pengguna.
POSSIBLE_RISK dan NO_DATA
Jika menerima verdict ini, minta pengguna untuk memeriksa apakah Play Protect sudah diaktifkan dan telah melakukan pemindaian. NO_DATA hanya akan muncul dalam situasi yang jarang terjadi.
MEDIUM_RISK dan HIGH_RISK
Bergantung pada toleransi risiko, Anda dapat meminta pengguna untuk meluncurkan Play Protect dan mengambil tindakan terhadap peringatan Play Protect. Jika pengguna tidak dapat memenuhi persyaratan ini, Anda dapat memblokirnya dari tindakan server.