Membuat permintaan API klasik

Jika hanya berencana membuat permintaan API standar yang sesuai untuk sebagian besar developer, Anda dapat langsung ke bagian verdict integritas. Halaman ini menjelaskan cara membuat permintaan API klasik untuk verdict integritas yang didukung di Android 4.4 (level API 19) atau yang lebih tinggi.

Pertimbangan

Membandingkan permintaan standar dan klasik

Anda dapat membuat permintaan standar, permintaan klasik, atau kombinasi keduanya, bergantung pada kebutuhan anti-penyalahgunaan dan keamanan aplikasi. Permintaan standar cocok untuk semua aplikasi dan game serta dapat digunakan untuk memeriksa apakah tindakan atau panggilan server tersebut sah, sekaligus mendelegasikan beberapa perlindungan terhadap kemampuan replay dan pemindahan yang tidak sah ke Google Play. Permintaan klasik lebih mahal untuk dibuat dan Anda bertanggung jawab untuk menerapkannya dengan benar untuk melindungi dari pemindahan yang tidak sah dan jenis serangan tertentu. Permintaan klasik harus dibuat lebih jarang daripada permintaan standar, misalnya sebagai permintaan satu kali untuk memeriksa apakah tindakan yang sangat bernilai atau sensitif adalah asli.

Tabel berikut menyoroti perbedaan utama antara kedua jenis permintaan:

Permintaan API standar Permintaan API klasik
Prasyarat
Versi Android SDK minimum yang diperlukan Android 5.0 (API Level 21) atau yang lebih tinggi Android 4.4 (level API 19) atau yang lebih tinggi
Persyaratan Google Play Layanan Google Play Store dan Google Play Layanan Google Play Store dan Google Play
Detail integrasi
Pemanasan API diperlukan ✔️ (beberapa detik)
Latensi permintaan umum Beberapa ratus milidetik Beberapa detik
Potensi frekuensi permintaan Sering (pemeriksaan on demand untuk tindakan atau permintaan apa pun) Jarang (pemeriksaan satu kali untuk tindakan bernilai tertinggi atau permintaan paling sensitif)
Waktu tunggu Sebagian besar pemanasan berlangsung kurang dari 10 detik tetapi melibatkan panggilan server, sehingga disarankan untuk menggunakan waktu tunggu yang lama (misalnya 1 menit). Permintaan verdict dilakukan di sisi klien Sebagian besar permintaan berdurasi kurang dari 10 detik, tetapi melibatkan panggilan server, sehingga direkomendasikan untuk menggunakan waktu tunggu yang lama (misalnya 1 menit)
Token verdict integritas
Berisi detail perangkat, aplikasi, dan akun ✔️ ✔️
Penyimpanan token dalam cache Penyimpanan dalam cache di perangkat yang dilindungi oleh Google Play Tidak direkomendasikan
Mendekripsi dan memverifikasi token melalui server Google Play ✔️ ✔️
Latensi permintaan server-ke-server dekripsi umum Puluhan milidetik dengan ketersediaan 99.9% Puluhan milidetik dengan ketersediaan 99.9%
Mendekripsi dan memverifikasi token secara lokal di lingkungan server yang aman ✔️
Mendekripsi dan memverifikasi token sisi klien
Keaktualan verdict integritas Beberapa penyimpanan dalam cache dan pemuatan ulang otomatis oleh Google Play Semua verdict dihitung ulang pada setiap permintaan
Batasan
Permintaan per aplikasi per hari 10.000 secara default (peningkatan dapat diminta) 10.000 secara default (peningkatan dapat diminta)
Permintaan per instance aplikasi per menit Pemanasan: 5 per menit
Token integritas: Tidak ada batasan publik*
Token integritas: 5 per menit
Perlindungan
Mengurangi modifikasi tidak sah dan serangan serupa Menggunakan kolom requestHash Menggunakan kolom nonce dengan binding konten berdasarkan data permintaan
Mengurangi replay dan serangan serupa Mitigasi otomatis oleh Google Play Menggunakan kolom nonce dengan logika sisi server

* Semua permintaan, termasuk permintaan tanpa batasan publik, tunduk pada batasan defensif non-publik pada nilai tinggi

Membuat permintaan klasik secara jarang

Menghasilkan token integritas menggunakan waktu, data, serta baterai, dan setiap aplikasi memiliki jumlah permintaan klasik maksimum yang dapat dibuat per hari. Oleh karena itu, Anda hanya boleh membuat permintaan klasik untuk memeriksa apakah nilai tertinggi atau tindakan yang paling sensitif adalah asli, jika Anda menginginkan jaminan tambahan untuk permintaan standar. Anda tidak boleh membuat permintaan klasik untuk tindakan berfrekuensi tinggi atau bernilai rendah. Jangan membuat permintaan klasik setiap kali aplikasi masuk ke latar depan atau setiap beberapa menit di latar belakang, dan hindari memanggil dari sejumlah besar perangkat secara bersamaan. Aplikasi yang melakukan terlalu banyak panggilan permintaan klasik dapat dibatasi untuk melindungi pengguna dari implementasi yang salah.

Menghindari menyimpan verdict dalam cache

Menyimpan verdict dalam cache akan meningkatkan risiko serangan seperti pemindahan yang tidak sah dan replay, dengan verdict yang baik akan digunakan kembali dari lingkungan yang tidak tepercaya. Jika Anda mempertimbangkan untuk membuat permintaan klasik, lalu menyimpannya dalam cache untuk digunakan nanti, sebaiknya lakukan permintaan standar on demand. Permintaan standar memerlukan penyimpanan dalam cache di perangkat, tetapi Google Play menggunakan teknik perlindungan tambahan untuk mengurangi risiko serangan replay dan pemindahan yang tidak sah.

Menggunakan kolom nonce untuk melindungi permintaan klasik

Play Integrity API menawarkan kolom yang disebut nonce, yang dapat digunakan untuk melindungi aplikasi Anda lebih lanjut dari serangan tertentu, seperti serangan replay dan modifikasi yang tidak sah. Play Integrity API menampilkan nilai yang Anda tetapkan dalam kolom ini, di dalam respons integritas yang ditandatangani. Ikuti dengan cermat panduan tentang cara membuat nonce untuk melindungi aplikasi Anda dari serangan.

Mencoba lagi permintaan klasik dengan backoff eksponensial

Kondisi lingkungan, seperti koneksi Internet yang tidak stabil atau perangkat yang kelebihan beban, dapat menyebabkan kegagalan pemeriksaan integritas perangkat. Hal ini dapat menyebabkan tidak adanya label yang dibuat untuk perangkat yang tepercaya. Untuk mengurangi skenario ini, sertakan opsi coba lagi dengan backoff eksponensial.

Ringkasan

Diagram urutan yang menunjukkan desain tingkat tinggi Play Integrity
API.

Saat pengguna melakukan tindakan bernilai tinggi di aplikasi yang ingin Anda lindungi dengan pemeriksaan integritas, selesaikan langkah-langkah berikut:

  1. Backend sisi server aplikasi akan membuat dan mengirimkan nilai unik ke logika sisi klien. Langkah-langkah yang tersisa mengacu ke logika ini sebagai "aplikasi" Anda.
  2. Aplikasi Anda akan membuat nonce dari nilai unik dan konten tindakan bernilai tinggi Anda. Lalu, aplikasi akan memanggil Play Integrity API yang meneruskan nonce tersebut.
  3. Aplikasi akan menerima verdict yang ditandatangani dan dienkripsi dari Play Integrity API.
  4. Aplikasi akan meneruskan verdict yang ditandatangani dan dienkripsi ke backend aplikasi.
  5. Backend aplikasi akan mengirimkan verdict ke server Google Play. Server Google Play mendekripsi dan memverifikasi verdict tersebut, yang akan menampilkan hasilnya ke backend aplikasi Anda.
  6. Backend aplikasi akan memutuskan cara melanjutkan, berdasarkan sinyal yang dimuat dalam payload token.
  7. Backend aplikasi akan mengirimkan hasil keputusan ke aplikasi.

Membuat nonce

Saat melindungi tindakan di aplikasi dengan Play Integrity API, Anda dapat memanfaatkan kolom nonce untuk mengurangi jenis serangan tertentu, seperti serangan modifikasi tidak sah person-in-the-middle (PITM) dan serangan replay. Play Integrity API menampilkan nilai yang Anda tetapkan dalam kolom ini, di dalam respons integritas yang ditandatangani.

Nilai yang ditetapkan di kolom nonce harus diformat dengan benar:

  • String
  • URL-safe
  • Dienkode sebagai Base64 dan non-wrapping
  • Minimum 16 karakter
  • Maksimum 500 karakter

Berikut adalah beberapa cara umum untuk menggunakan kolom nonce di Play Integrity API. Untuk mendapatkan perlindungan terkuat dari nonce, Anda dapat menggabungkan metode di bawah ini.

Menyertakan hash permintaan untuk melindungi dari modifikasi tidak sah

Anda dapat menggunakan parameter nonce dalam permintaan API klasik yang serupa dengan parameter requestHash dalam permintaan API standar untuk melindungi konten permintaan terhadap modifikasi tidak sah.

Saat Anda meminta verdict integritas:

  1. Hitung ringkasan semua parameter permintaan penting (misalnya, SHA256 dari serialisasi permintaan stabil) dari tindakan pengguna atau permintaan server yang terjadi.
  2. Gunakan setNonce untuk menetapkan kolom nonce ke nilai ringkasan yang dihitung.

Saat Anda menerima verdict integritas:

  1. Dekode dan verifikasi token integritas, lalu dapatkan ringkasan dari kolom nonce.
  2. Hitung ringkasan permintaan dengan cara yang sama seperti pada aplikasi (misalnya SHA256 dari serialisasi permintaan stabil).
  3. Bandingkan ringkasan sisi aplikasi dan sisi server. Jika tidak cocok, permintaan tidak dapat dipercaya.

Menyertakan nilai unik untuk melindungi dari serangan replay

Untuk mencegah pengguna berbahaya menggunakan kembali respons sebelumnya dari Play Integrity API, Anda dapat menggunakan kolom nonce untuk mengidentifikasi setiap pesan secara unik.

Saat Anda meminta verdict integritas:

  1. Mendapatkan nilai unik secara global dengan cara yang tidak dapat diprediksi oleh pengguna yang berbahaya. Misalnya, angka acak yang diamankan secara kriptografis yang dihasilkan pada sisi server dapat berupa nilai tersebut, atau ID yang sudah ada sebelumnya, seperti ID sesi atau transaksi. Varian yang lebih sederhana dan kurang aman adalah untuk menghasilkan angka acak di perangkat. Sebaiknya buat nilai 128 bit atau lebih besar.
  2. Panggil setNonce() untuk menetapkan kolom nonce ke nilai unik dari langkah 1.

Saat Anda menerima verdict integritas:

  1. Dekode dan verifikasi token integritas, lalu dapatkan nilai unik dari kolom nonce.
  2. Jika nilai dari langkah 1 dihasilkan di server, pastikan nilai unik yang diterima adalah salah satu nilai yang dihasilkan, dan digunakan untuk pertama kalinya (server Anda harus menyimpan data nilai yang dihasilkan untuk durasi yang sesuai). Jika nilai unik yang diterima telah digunakan atau tidak muncul dalam data, tolak permintaan tersebut
  3. Atau, jika nilai unik dibuat di perangkat, pastikan nilai yang diterima digunakan untuk pertama kalinya (server Anda harus menyimpan data nilai yang telah terlihat untuk durasi yang sesuai). Jika nilai unik yang diterima sudah digunakan, tolak permintaan tersebut.

Menggabungkan kedua perlindungan terhadap modifikasi tidak sah dan serangan replay (direkomendasikan)

Kolom nonce dapat digunakan untuk melindungi dari modifikasi tidak sah dan serangan replay secara bersamaan. Untuk melakukannya, buat nilai unik seperti yang dijelaskan di atas, dan sertakan sebagai bagian dari permintaan Anda. Kemudian, hitung hash permintaan, pastikan untuk menyertakan nilai unik sebagai bagian dari hash. Implementasi yang menggabungkan kedua pendekatan tersebut adalah sebagai berikut:

Saat Anda meminta verdict integritas:

  1. Pengguna memulai tindakan bernilai tinggi.
  2. Dapatkan nilai unik untuk tindakan ini seperti yang dijelaskan di bagian Menyertakan nilai unik untuk melindungi dari serangan replay.
  3. Siapkan pesan yang ingin Anda lindungi. Sertakan nilai unik dari langkah 2 dalam pesan.
  4. Aplikasi Anda menghitung ringkasan pesan yang ingin dilindungi, seperti yang dijelaskan di bagian Menyertakan hash permintaan untuk melindungi dari modifikasi tidak sah. Karena pesan berisi nilai unik, nilai unik merupakan bagian dari hash.
  5. Gunakan setNonce() untuk menetapkan kolom nonce ke ringkasan yang dihitung dari langkah sebelumnya.

Saat Anda menerima verdict integritas:

  1. Dapatkan nilai unik dari permintaan
  2. Dekode dan verifikasi token integritas, lalu dapatkan ringkasan dari kolom nonce.
  3. Seperti dijelaskan di bagian Menyertakan hash permintaan untuk melindungi dari modifikasi tidak sah, hitung ulang ringkasan di sisi server, dan periksa apakah ringkasan itu cocok dengan ringkasan yang diperoleh dari token integritas.
  4. Seperti dijelaskan di bagian Menyertakan nilai unik untuk melindungi dari serangan replay, periksa validitas nilai unik.

Diagram urutan berikut menggambarkan langkah-langkah ini dengan nonce sisi server:

Diagram urutan yang menunjukkan cara melindungi dari modifikasi tidak sah dan serangan
replay

Meminta verdict integritas

Setelah membuat nonce, Anda dapat meminta verdict integritas dari Google Play. Caranya, selesaikan langkah-langkah berikut:

  1. Buat IntegrityManager seperti yang ditunjukkan dalam contoh berikut.
  2. Buat IntegrityTokenRequest yang menyediakan nonce melalui metode setNonce() di builder terkait. Aplikasi yang didistribusikan secara eksklusif di luar Google Play dan SDK juga harus menentukan nomor project Google Cloud melalui metode setCloudProjectNumber(). Aplikasi di Google Play ditautkan ke project Cloud di Konsol Play dan tidak perlu menetapkan nomor project Cloud dalam permintaan.
  3. Gunakan pengelola untuk memanggil requestIntegrityToken() yang menyediakan IntegrityTokenRequest.

Kotlin

// Receive the nonce from the secure server.
val nonce: String = ...

// Create an instance of a manager.
val integrityManager =
    IntegrityManagerFactory.create(applicationContext)

// Request the integrity token by providing a nonce.
val integrityTokenResponse: Task<IntegrityTokenResponse> =
    integrityManager.requestIntegrityToken(
        IntegrityTokenRequest.builder()
             .setNonce(nonce)
             .build())

Java

import com.google.android.gms.tasks.Task; ...

// Receive the nonce from the secure server.
String nonce = ...

// Create an instance of a manager.
IntegrityManager integrityManager =
    IntegrityManagerFactory.create(getApplicationContext());

// Request the integrity token by providing a nonce.
Task<IntegrityTokenResponse> integrityTokenResponse =
    integrityManager
        .requestIntegrityToken(
            IntegrityTokenRequest.builder().setNonce(nonce).build());

Unity

IEnumerator RequestIntegrityTokenCoroutine() {
    // Receive the nonce from the secure server.
    var nonce = ...

    // Create an instance of a manager.
    var integrityManager = new IntegrityManager();

    // Request the integrity token by providing a nonce.
    var tokenRequest = new IntegrityTokenRequest(nonce);
    var requestIntegrityTokenOperation =
        integrityManager.RequestIntegrityToken(tokenRequest);

    // Wait for PlayAsyncOperation to complete.
    yield return requestIntegrityTokenOperation;

    // Check the resulting error code.
    if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError)
    {
        AppendStatusLog("IntegrityAsyncOperation failed with error: " +
                requestIntegrityTokenOperation.Error);
        yield break;
    }

    // Get the response.
    var tokenResponse = requestIntegrityTokenOperation.GetResult();
}

Native

/// Create an IntegrityTokenRequest opaque object.
const char* nonce = RequestNonceFromServer();
IntegrityTokenRequest* request;
IntegrityTokenRequest_create(&request);
IntegrityTokenRequest_setNonce(request, nonce);

/// Prepare an IntegrityTokenResponse opaque type pointer and call
/// IntegerityManager_requestIntegrityToken().
IntegrityTokenResponse* response;
IntegrityErrorCode error_code =
        IntegrityManager_requestIntegrityToken(request, &response);

/// ...
/// Proceed to polling iff error_code == INTEGRITY_NO_ERROR
if (error_code != INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.
/// Note, the polling shouldn't block the thread where the IntegrityManager
/// is running.

IntegrityResponseStatus response_status;

/// Check for error codes.
IntegrityErrorCode error_code =
        IntegrityTokenResponse_getStatus(response, &response_status);
if (error_code == INTEGRITY_NO_ERROR
    && response_status == INTEGRITY_RESPONSE_COMPLETED)
{
    const char* integrity_token = IntegrityTokenResponse_getToken(response);
    SendTokenToServer(integrity_token);
}
/// ...
/// Remember to free up resources.
IntegrityTokenRequest_destroy(request);
IntegrityTokenResponse_destroy(response);
IntegrityManager_destroy();

Mendekripsi dan memverifikasi verdict integritas

Saat Anda meminta verdict integritas, Play Integrity API akan menyediakan token respons yang ditandatangani. nonce yang Anda sertakan dalam permintaan Anda akan menjadi bagian dari token respons.

Format token

Token berupa JSON Web Token (JWT) bertingkat, yaitu JSON Web Encryption (JWE) dari JSON Web Signature (JWS). Komponen JWE dan JWS direpresentasikan menggunakan serialisasi ringkas.

Algoritma enkripsi dan penandatanganan didukung dengan baik di berbagai implementasi JWT:

  • JWE menggunakan A256KW untuk alg dan A256GCM untuk enc

  • JWS menggunakan ES256.

Mendekripsi dan memverifikasi di server Google (direkomendasikan)

Play Integrity API memungkinkan Anda mendekripsi dan memverifikasi verdict integritas di server Google, yang meningkatkan keamanan aplikasi. Untuk melakukannya, selesaikan langkah-langkah berikut:

  1. Buat akun layanan dalam project Google Cloud yang ditautkan ke aplikasi Anda. Selama proses pembuatan akun ini, Anda perlu memberikan peran Service Account User dan Service Usage Consumer kepada akun layanan Anda.
  2. Di server aplikasi Anda, ambil token akses dari kredensial akun layanan menggunakan cakupan playintegrity, dan buat permintaan berikut:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Baca respons JSON.

Mendekripsi dan memverifikasi secara lokal

Jika memilih untuk mengelola dan mendownload kunci enkripsi respons, Anda dapat mendekripsi dan memverifikasi token yang ditampilkan dalam lingkungan server Anda yang aman. Anda bisa mendapatkan token yang ditampilkan menggunakan metode IntegrityTokenResponse#token().

Contoh berikut menunjukkan cara mendekode kunci AES dan kunci EC publik yang dienkode DER untuk verifikasi tanda tangan dari Konsol Play ke kunci bahasa tertentu (dalam hal ini, bahasa pemograman Java) di backend aplikasi. Perhatikan bahwa kunci dienkode dengan base64 menggunakan flag default.

Kotlin

// base64OfEncodedDecryptionKey is provided through Play Console.
var decryptionKeyBytes: ByteArray =
    Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT)

// Deserialized encryption (symmetric) key.
var decryptionKey: SecretKey = SecretKeySpec(
    decryptionKeyBytes,
    /* offset= */ 0,
    AES_KEY_SIZE_BYTES,
    AES_KEY_TYPE
)

// base64OfEncodedVerificationKey is provided through Play Console.
var encodedVerificationKey: ByteArray =
    Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT)

// Deserialized verification (public) key.
var verificationKey: PublicKey = KeyFactory.getInstance(EC_KEY_TYPE)
    .generatePublic(X509EncodedKeySpec(encodedVerificationKey))

Java


// base64OfEncodedDecryptionKey is provided through Play Console.
byte[] decryptionKeyBytes =
    Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT);

// Deserialized encryption (symmetric) key.
SecretKey decryptionKey =
    new SecretKeySpec(
        decryptionKeyBytes,
        /* offset= */ 0,
        AES_KEY_SIZE_BYTES,
        AES_KEY_TYPE);

// base64OfEncodedVerificationKey is provided through Play Console.
byte[] encodedVerificationKey =
    Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT);
// Deserialized verification (public) key.
PublicKey verificationKey =
    KeyFactory.getInstance(EC_KEY_TYPE)
        .generatePublic(new X509EncodedKeySpec(encodedVerificationKey));

Berikutnya, gunakan kunci ini untuk mendekripsi token integritas (bagian JWE) terlebih dahulu, lalu verifikasi dan ekstrak bagian JWS bertingkat.

Kotlin

val jwe: JsonWebEncryption =
    JsonWebStructure.fromCompactSerialization(integrityToken) as JsonWebEncryption
jwe.setKey(decryptionKey)

// This also decrypts the JWE token.
val compactJws: String = jwe.getPayload()

val jws: JsonWebSignature =
    JsonWebStructure.fromCompactSerialization(compactJws) as JsonWebSignature
jws.setKey(verificationKey)

// This also verifies the signature.
val payload: String = jws.getPayload()

Java

JsonWebEncryption jwe =
    (JsonWebEncryption)JsonWebStructure
        .fromCompactSerialization(integrityToken);
jwe.setKey(decryptionKey);

// This also decrypts the JWE token.
String compactJws = jwe.getPayload();

JsonWebSignature jws =
    (JsonWebSignature) JsonWebStructure.fromCompactSerialization(compactJws);
jws.setKey(verificationKey);

// This also verifies the signature.
String payload = jws.getPayload();

Payload yang dihasilkan adalah token teks biasa yang berisi verdict integritas.