Tworzenie klasycznego żądania do interfejsu API

Jeśli zamierzasz tworzyć tylko standardowe żądania do interfejsu API, które są odpowiednie dla większości deweloperów, możesz przejść od razu do oceny integralności. Na tej stronie dowiesz się, jak tworzyć klasyczne żądania do interfejsu API umożliwiające ocenę integralności, które są obsługiwane na Androidzie 4.4 (poziom interfejsu API 19) lub nowszym.

co należy wziąć pod uwagę

Porównanie żądań standardowych i klasycznych

Możesz wysyłać żądania standardowe, klasyczne lub ich kombinację w zależności od potrzeb aplikacji w zakresie bezpieczeństwa i zapobiegania nadużyciom. Żądania standardowe są odpowiednie dla wszystkich aplikacji i gier i umożliwiają sprawdzenie, czy każde działanie lub wywołanie serwera jest autentyczne, a jednocześnie zapewnia ochronę Google Play przed ponownym odtwarzaniem i wydobyciem danych. Klasyczne żądania są droższe i to Ty odpowiadasz za ich prawidłowe wdrożenie, które chroni przed wydobyciem danych i niektórymi rodzajami ataków. Żądania klasyczne powinny być wysyłane rzadziej niż standardowe żądania, np. jednorazowo, aby sprawdzić, czy działanie o dużej wartości lub poufne dane jest autentyczne.

W tabeli poniżej przedstawiliśmy najważniejsze różnice między tymi 2 typami żądań:

Standardowe żądanie do interfejsu API Klasyczne żądanie do interfejsu API
Wymagania wstępne
Wymagana minimalna wersja pakietu SDK na Androida Android 5.0 (poziom interfejsu API 21) lub nowszy Android 4.4 (poziom interfejsu API 19) lub nowszy
Wymagania Google Play Sklep Google Play i Usługi Google Play Sklep Google Play i Usługi Google Play
Szczegóły integracji
Wymagane rozgrzewka interfejsu API ✔️ (kilka sekund)
Typowe opóźnienie żądania Kilkaset milisekund Kilka sekund
Potencjalna częstotliwość żądań Częste (sprawdzanie na żądanie w przypadku działań i żądań) Rzadkie (jednorazowe sprawdzanie w przypadku działań o największej wartości lub najbardziej wrażliwych żądań)
tymczasowe zawieszenia użytkowników Większość rozgrzewek trwa krócej niż 10 s, ale wiąże się z wywołaniem serwera, dlatego zaleca się długi czas oczekiwania (np. 1 minutę). Żądania oceny są realizowane po stronie klienta Większość żądań ma wartość mniejszą niż 10 sekund, ale obejmują one wywołanie serwera, dlatego zalecamy długi limit czasu (np. 1 minutę)
Token oceny integralności
Zawiera informacje o urządzeniu, aplikacji i koncie
Buforowanie tokena Chroniona pamięć podręczna na urządzeniu dzięki Google Play Niezalecane
Odszyfrowywanie i weryfikację tokena przez serwer Google Play
Typowy czas oczekiwania na żądanie odszyfrowywania między serwerami 10 s milisekund z dostępnością „3-9s” 10 s milisekund z dostępnością „3-9s”
Odszyfruj i zweryfikuj token lokalnie w bezpiecznym środowisku serwera
Odszyfrowywanie i weryfikacja tokena po stronie klienta
Aktualność oceny integralności Niektóre automatyczne buforowanie i odświeżanie przez Google Play Wszystkie oceny są ponownie obliczane dla każdego żądania
Limity
Żądania na aplikację dziennie Domyślnie 10 000 (można poprosić o zwiększenie) Domyślnie 10 000 (można poprosić o zwiększenie)
Żądania na instancję aplikacji na minutę Rozgrzewka: 5 na minutę
Tokeny integralności: bez limitu publicznego*
Tokeny integralności: 5 na minutę
Ochrona
Zapobieganie manipulacjom i podobnym atakom Użyj pola requestHash Użyj pola nonce z powiązaniem treści na podstawie danych żądania
Minimalizuj ryzyko ponownych ataków i podobnych ataków Automatyczne ograniczanie ryzyka przez Google Play Użyj pola nonce z logiką po stronie serwera

* Wszystkie żądania, również te bez ograniczeń publicznych, podlegają niepublicznym limitom ochronnym o wysokich wartościach.

Rzadko rób żądania klasyczne

Wygenerowanie tokena integralności wymaga czasu, danych i baterii, a każda aplikacja ma maksymalną liczbę żądań klasycznych, które może wysłać dziennie. Dlatego, jeśli chcesz uzyskać dodatkową gwarancję na żądanie standardowe, wysyłaj żądania klasyczne, aby sprawdzić najwyższą wartość, lub działania o charakterze wrażliwym, które są prawdziwe. Nie wysyłaj klasycznych żądań dotyczących działań o wysokiej częstotliwości lub niskiej wartości. Nie wysyłaj żądań klasycznych za każdym razem, gdy aplikacja działa na pierwszym planie ani co kilka minut w tle. Unikaj też wywołań z dużej liczby urządzeń jednocześnie. Aplikacja wykonująca zbyt wiele wywołań klasycznych żądań może zostać ograniczona, aby chronić użytkowników przed nieprawidłowymi implementacjami.

Unikaj wyników buforowania

Zapisanie wyniku w pamięci podręcznej zwiększa ryzyko ataków, takich jak wydobycie i ponowne odtworzenie, w przypadku których dobra decyzja została wykorzystana z niezaufanego środowiska. Jeśli rozważasz utworzenie klasycznego żądania i zapisanie go w pamięci podręcznej w celu późniejszego użycia, zalecamy wykonanie standardowego żądania na żądanie. Standardowe żądania wymagają częściowo buforowania w pamięci podręcznej na urządzeniu, ale Google Play stosuje dodatkowe metody ochrony, aby zmniejszyć ryzyko ponownego ataków i wydobycia danych.

Używaj pola liczby jednorazowej do ochrony żądań klasycznych

Interfejs Play Integrity API zawiera pole o nazwie nonce, które pozwala jeszcze lepiej chronić aplikację przed niektórymi atakami, takimi jak ponowne odtwarzanie czy manipulacje. Interfejs Play Integrity API zwraca wartość ustawioną w tym polu w podpisanej odpowiedzi dotyczącej integralności. Postępuj dokładnie zgodnie ze wskazówkami dotyczącymi generowania kodów jednorazowych, aby chronić aplikację przed atakami.

Ponawiaj próby żądań klasycznych ze wzrastającym czasem do ponowienia

Warunki środowiskowe, takie jak niestabilne połączenie z internetem lub przeciążenie urządzenia, mogą spowodować niepowodzenie testów integralności. Może to spowodować, że dla urządzenia, które jest wiarygodne, nie będą generowane żadne etykiety. Aby ograniczyć takie przypadki, uwzględnij opcję ponawiania ze wzrastającym czasem do ponowienia.

Przegląd

Schemat sekwencji przedstawiający ogólny projekt interfejsu Play Integrity API.

Gdy użytkownik wykona w Twojej aplikacji wartościowe działanie, które chcesz chronić za pomocą kontroli integralności, wykonaj te czynności:

  1. Backend aplikacji po stronie serwera generuje i wysyła unikalną wartość do logiki po stronie klienta. W pozostałych krokach ta logika jest nazywana „aplikacją”.
  2. Aplikacja tworzy nonce na podstawie unikalnej wartości i treści działania o dużej wartości. Następnie wywołuje interfejs Play Integrity API, podając wartość nonce.
  3. Twoja aplikacja otrzymuje podpisaną i zaszyfrowaną ocenę od interfejsu Play Integrity API.
  4. Aplikacja przekazuje podpisaną i zaszyfrowaną ocenę do backendu aplikacji.
  5. Backend aplikacji wysyła ocenę do serwera Google Play. Serwer Google Play odszyfrowuje i weryfikuje ocenę, zwracając wyniki do backendu aplikacji.
  6. Backend aplikacji decyduje, co zrobić dalej, na podstawie sygnałów zawartych w ładunku tokena.
  7. Backend aplikacji wysyła do niej wyniki decyzji.

Wygeneruj liczbę jednorazową

Gdy chronisz działanie w aplikacji za pomocą interfejsu Play Integrity API, możesz wykorzystać pole nonce do ograniczania niektórych typów ataków, takich jak zmanipulowanie w czasie rzeczywistym (PITM) czy ataki typu „replay”. Interfejs Play Integrity API zwraca wartość ustawioną w tym polu w odpowiedzi integralności ze znakiem.

Wartość ustawiona w polu nonce musi być prawidłowo sformatowana:

  • String
  • Odpowiednie w adresie URL
  • Zakodowane w formacie Base64 bez zawijania wierszy
  • Minimum 16 znaków
  • Maksymalnie 500 znaków.

Poniżej znajdziesz kilka typowych sposobów korzystania z pola nonce w interfejsie Play Interity API. Aby uzyskać najsilniejszą ochronę nonce, możesz połączyć poniższe metody.

Dołącz hasz żądania, aby zabezpieczyć się przed modyfikacją

Aby chronić treść żądania przed modyfikacją, możesz użyć parametru nonce w klasycznym żądaniu do interfejsu API, podobnie jak parametru requestHash w standardowym żądaniu do interfejsu API.

Gdy poprosisz o ocenę integralności:

  1. Oblicza podsumowanie wszystkich krytycznych parametrów żądań (np. SHA256 stabilnej serializacji żądania) z występującego działania użytkownika lub żądania serwera.
  2. Użyj setNonce, aby w polu nonce ustawić wartość obliczonego skrótu.

Gdy otrzymasz ocenę integralności:

  1. Zdekoduj i zweryfikuj token integralności oraz uzyskaj skrót z pola nonce.
  2. Oblicz skrót żądania w taki sam sposób jak w aplikacji (np. za pomocą algorytmu SHA256 serializacji stabilnego żądania).
  3. Porównaj skróty po stronie aplikacji i serwera. Jeśli żądania się nie zgadzają, żądanie nie jest wiarygodne.

Uwzględniaj unikalne wartości, aby chronić się przed atakami typu replay

Aby szkodliwi użytkownicy nie wykorzystali poprzednich odpowiedzi z interfejsu Play Integrity API, możesz używać pola nonce do jednoznacznej identyfikacji każdej wiadomości.

Gdy poprosisz o ocenę integralności:

  1. Uzyskanie unikalnej globalnie wartości w sposób, którego szkodliwi użytkownicy nie mogą przewidzieć. Na przykład zabezpieczona kryptograficznie losowa liczba losowa wygenerowana po stronie serwera może być taką wartością lub wcześniej utworzonym identyfikatorem, takim jak sesja lub identyfikator transakcji. Prostszym i mniej bezpiecznym wariantem jest wygenerowanie losowej liczby z urządzenia. Zalecamy tworzenie wartości 128-bitowych lub większych.
  2. Wywołaj setNonce(), aby w polu nonce ustawić unikalną wartość uzyskaną w kroku 1.

Gdy otrzymasz ocenę integralności:

  1. Zdekoduj i zweryfikuj token integralności oraz uzyskaj unikalną wartość z pola nonce.
  2. Jeśli wartość z kroku 1 została wygenerowana na serwerze, sprawdź, czy otrzymana unikalna wartość była jedną z wygenerowanych wartości i czy jest używana po raz pierwszy (serwer musi przez odpowiedni okres przechowywać rejestr wygenerowanych wartości). Jeśli otrzymana unikalna wartość została już użyta lub nie ma jej w rekordzie, odrzuć prośbę
  3. W przeciwnym razie, jeśli unikalna wartość została wygenerowana na urządzeniu, sprawdź, czy otrzymana wartość jest używana po raz pierwszy (serwer musi przez odpowiedni czas prowadzić rejestr wartości, które zostały już zarejestrowane). Jeśli otrzymana unikalna wartość została już użyta, odrzuć prośbę.

Połącz oba zabezpieczenia przed manipulacjami i atakami typu replay (zalecane)

Pola nonce można używać do jednoczesnej ochrony przed manipulacjami i atakami typu replay. Aby to zrobić, wygeneruj unikalną wartość w sposób opisany powyżej i uwzględnij ją w żądaniu. Następnie oblicz hasz żądania, uwzględniając w nim unikalną wartość. Implementacja łącząca oba podejścia jest następująca:

Gdy poprosisz o ocenę integralności:

  1. Użytkownik inicjuje działanie o dużej wartości.
  2. Ustaw dla tego działania unikalną wartość zgodnie z opisem w sekcji Uwzględnianie unikalnych wartości w celu ochrony przed atakami typu replay.
  3. Przygotuj wiadomość, którą chcesz chronić. Umieść w wiadomości unikalną wartość z kroku 2.
  4. Aplikacja oblicza skrót wiadomości, którą chce chronić w sposób opisany w sekcji Uwzględnianie skrótu żądania w celu ochrony przed nieuprawnionymi modyfikacjami. Wiadomość zawiera unikalną wartość, więc jest ona częścią skrótu.
  5. Użyj setNonce(), aby w polu nonce ustawić obliczony skrót z poprzedniego kroku.

Gdy otrzymasz ocenę integralności:

  1. Uzyskanie unikalnej wartości z żądania
  2. Zdekoduj i zweryfikuj token integralności oraz uzyskaj skrót z pola nonce.
  3. Zgodnie z opisem w sekcji Dołącz hasz żądania w celu zabezpieczenia przed manipulacją ponownie oblicz skrót po stronie serwera i sprawdź, czy jest zgodny z skrótem uzyskanym z tokena integralności.
  4. Jak opisano w sekcji Uwzględniaj unikalne wartości w celu ochrony przed atakami typu replay, sprawdź poprawność tej unikalnej wartości.

Poniższy diagram przedstawia te kroki z użyciem nonce po stronie serwera:

Schemat sekwencji pokazujący, jak chronić się przed
manipulacjami i powtarzającymi się atakami

Poproś o ocenę integralności

Po wygenerowaniu pliku nonce możesz poprosić o ocenę integralności do Google Play. Aby to zrobić:

  1. Utwórz IntegrityManager w sposób opisany w przykładach poniżej.
  2. Utwórz IntegrityTokenRequest, podając nonce za pomocą metody setNonce() w powiązanym kreatorze. Aplikacje rozpowszechniane wyłącznie poza Google Play i pakietami SDK również muszą podawać numer projektu Google Cloud za pomocą metody setCloudProjectNumber(). Aplikacje z Google Play są połączone z projektem Cloud w Konsoli Play i nie trzeba w żądaniu podawać numeru projektu Cloud.
  3. Użyj menedżera, aby wywołać requestIntegrityToken() i podać 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());

Jedność

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();
}

Reklamy natywne

/// 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();

Odszyfruj i zweryfikuj ocenę integralności

Gdy poprosisz o ocenę integralności, interfejs Play Integrity API udostępni podpisany token odpowiedzi. Identyfikator nonce podany w żądaniu stanie się częścią tokena odpowiedzi.

Format tokena

Token to zagnieżdżony token internetowy JSON (JWT) JSON Web Encryption (JWE) JSON Web Signature (JWS). Komponenty JWE i JWS są reprezentowane za pomocą kompaktowej serializacji.

Algorytmy szyfrowania / podpisywania są dobrze obsługiwane w różnych implementacjach tokena JWT:

  • JWE używa A256KW dla alg i A256GCM do enc

  • JWS używa kodu ES256.

Odszyfrowywanie i weryfikowanie danych na serwerach Google (zalecane)

Interfejs Play Integrity API umożliwia odszyfrowywanie i weryfikowanie oceny integralności na serwerach Google, co zwiększa bezpieczeństwo aplikacji. Aby to zrobić:

  1. Utwórz konto usługi w projekcie Google Cloud połączonym z Twoją aplikacją.
  2. Z serwera aplikacji pobierz token dostępu z danych logowania konta usługi, korzystając z zakresu playintegrity, i wykonaj to żądanie:

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

Odszyfruj i zweryfikuj lokalnie

Jeśli zdecydujesz się pobrać klucze szyfrowania odpowiedzi i nimi zarządzać, możesz odszyfrować i zweryfikować zwrócony token w swoim bezpiecznym środowisku serwera. Zwrócony token możesz uzyskać za pomocą metody IntegrityTokenResponse#token().

Przykład poniżej pokazuje, jak zdekodować klucz AES i publiczny klucz EC zakodowany w formacie DER na potrzeby weryfikacji podpisu z Konsoli Play do kluczy specyficznych dla danego języka (w naszym przypadku języka programowania Java) w backendzie aplikacji. Pamiętaj, że klucze są zakodowane w base64 z użyciem flag domyślnych.

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));

Następnie użyj tych kluczy do odszyfrowania tokena integralności (część JWE), a potem sprawdź i wyodrębnij zagnieżdżoną część JWS.

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();

Powstały w ten sposób ładunek jest zwykłym tokenem tekstowym, który zawiera oceny integralności.