Tworzenie klasycznego żądania do interfejsu API

Jeśli zamierzasz wysyłać tylko standardowe żądania do interfejsu API, które są odpowiednie dla większości deweloperów, możesz przejść do oceny integralności. Na tej stronie opisujemy tworzenie klasycznych żądań do interfejsu API na potrzeby oceny integralności, które są obsługiwane na Androidzie 4.4 (poziom interfejsu API 19) i nowszych.

co należy wziąć pod uwagę

Porównywanie żądań standardowych i klasycznych

Możesz wysyłać żądania standardowe, klasyczne lub oba te rodzaje żądań w zależności od potrzeb aplikacji w zakresie bezpieczeństwa i zapobiegania nadużyciom. Żądania standardowe są odpowiednie dla wszystkich aplikacji i gier. Za ich pomocą można sprawdzać, czy każde wywołanie serwera jest autentyczne, a jednocześnie przekazywać do Google Play pewien poziom ochrony przed możliwością ponownego odtwarzania i wydobycia. Żądania klasyczne są droższe i odpowiadasz za ich prawidłowe wdrożenie w celu ochrony przed wydobyciem informacji i niektórymi typami ataków. Żądania klasyczne powinny być wysyłane rzadziej niż żądania standardowe, np. jako jednorazowe, aby sprawdzić, czy bardzo wartościowe lub poufne działanie jest prawdziwe.

W tabeli poniżej podajemy 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 przygotowanie do użycia interfejsu API ✔️ (kilka sekund)
Typowy czas oczekiwania na żądanie Kilkaset milisekund Kilka sekund
Możliwa częstotliwość żądań Częste (na żądanie sprawdza, czy wystąpiło jakieś działanie lub żądanie) Rzadkie (jednorazowe sprawdzenie w przypadku działań o największej wartości lub najbardziej wrażliwych żądań)
tymczasowe zawieszenia użytkowników Większość rozgrzewek trwa poniżej 10 s, ale wiąże się z wywołaniem serwera, dlatego zalecany jest długi czas oczekiwania (np. 1 minuta). Żądania oceny są wykonywane po stronie klienta Większość żądań trwa krócej niż 10 s, ale wiąże się z połączeniem z serwera, dlatego zalecany jest długi czas oczekiwania (np.1 minuta).
Token oceny integralności
Zawiera szczegóły urządzenia, aplikacji i konta ✔️ ✔️
Buforowanie tokena Chronione buforowanie urządzenia na urządzeniu od Google Play Niezalecane
Odszyfrowywanie i weryfikowanie tokena przez serwer Google Play ✔️ ✔️
Typowe opóźnienie żądań między serwerami odszyfrowywania Czas działania to 10 milisekund z dostępnością systemu „trzydziewięć”. Czas działania to 10 milisekund z dostępnością systemu „trzydziewięć”.
Odszyfruj i zweryfikuj token lokalnie w bezpiecznym środowisku serwera ✔️
Odszyfrowywanie i weryfikację tokena po stronie klienta
Aktualność oceny integralności Częściowo automatyczne buforowanie i odświeżanie przez Google Play Wszystkie oceny przeliczone ponownie po każdym żądaniu
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)
Liczba żądań na instancję aplikacji na minutę Rozgrzewki: 5 na minutę
Tokeny integralności: bez limitu publicznego*
Tokeny integralności: 5 na minutę
Ochrona
ograniczanie ryzyka manipulacji i podobnych ataków; Użyj pola requestHash Użyj pola nonce z wiązaniem treści na podstawie danych żądania
Ogranicz liczbę powtórzeń i podobnych ataków Automatyczne ograniczanie ryzyka przez Google Play Użyj pola nonce z logiką po stronie serwera

* Wszystkie żądania, także te bez ograniczeń publicznych, podlegają niepublicznym granicom obrony przy wysokich wartościach.

Rzadkie wysyłanie żądań klasycznych

Wygenerowanie tokena integralności wymaga czasu, danych i baterii, a każda aplikacja może wysłać dziennie maksymalną liczbę żądań klasycznych. Jeśli chcesz uzyskać dodatkową gwarancję w stosunku do żądania standardowego, wysyłaj klasyczne żądania tylko w celu sprawdzenia najwyższej wartości lub jeśli najbardziej wrażliwe działania są autentyczne. Nie wysyłaj klasycznych żądań w przypadku działań o dużej lub małej wartości. Nie wysyłaj żądań klasycznych za każdym razem, gdy aplikacja działa na pierwszym planie, ani nie działa w tle. Unikaj też dzwonienia z dużej liczby urządzeń jednocześnie. Aplikacja wykonująca zbyt wiele wywołań żądań klasycznych może zostać ograniczona, aby chronić użytkowników przed nieprawidłowymi implementacjami.

Unikaj ocen w pamięci podręcznej

Przechowywanie oceny w pamięci podręcznej zwiększa ryzyko ataków, takich jak wydobycie i powtórzenie oceny, w których przypadku dobra ocena jest wykorzystywana z niezaufanego środowiska. Jeśli rozważasz utworzenie żądania klasycznego, a następnie przechowywanie go w pamięci podręcznej do późniejszego użycia, zalecamy wykonywanie standardowego żądania na żądanie. Standardowe żądania wymagają pewnego buforowania na urządzeniu, ale Google Play stosuje dodatkowe metody ochrony, aby zmniejszyć ryzyko ponownego odtwarzania ataków i wydobycia.

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

Interfejs Play Integrity API zawiera pole o nazwie nonce, które może służyć do dalszej ochrony aplikacji przed określonymi atakami, takimi jak ponowne odtwarzanie czy manipulacje. Interfejs Play Integrity API zwraca wartość ustawioną w tym polu w podpisanej odpowiedzi dotyczącej integralności. Uważnie postępuj zgodnie ze wskazówkami dotyczącymi generowania promptów, aby chronić aplikację przed atakami.

Ponawiaj żądania klasyczne ze wzrastającym czasem do ponowienia

Warunki środowiskowe, takie jak niestabilne połączenie z internetem lub przeciążone urządzenie, mogą spowodować niepowodzenie testów integralności. W rezultacie dla urządzenia, które jest w inny sposób wiarygodne, nie będą tworzone żadne etykiety. Aby ograniczyć takie scenariusze, dodaj opcję ponawiania ze wzrastającym czasem do ponowienia.

Przegląd

Diagram sekwencji pokazujący ogólny wygląd interfejsu Play Integrity API

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

  1. Backend Twojej aplikacji po stronie serwera generuje i wysyła unikalną wartość do logiki po stronie klienta. Pozostałe kroki określają logikę jako „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, przekazując nonce.
  3. Aplikacja otrzymuje podpisaną i zaszyfrowaną ocenę z interfejsu Play Integrity API.
  4. Aplikacja przekazuje podpisaną i zaszyfrowaną ocenę do jej backendu.
  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 określa dalsze kroki na podstawie sygnałów zawartych w ładunku tokena.
  7. Backend aplikacji wysyła do niej wyniki decyzji.

Generowanie liczby jednorazowej

Gdy zabezpieczasz działanie w aplikacji za pomocą interfejsu Play Integrity API, możesz wykorzystać pole nonce do łagodzenia określonych typów ataków, takich jak ataki polegające na manipulacji metodą PITM. Interfejs Play Integrity API zwraca wartość ustawioną w tym polu w odpowiedzi podpisanej integralności.

Wartość ustawiona w polu nonce musi być odpowiednio sformatowana:

  • String
  • Do wykorzystania w adresie URL
  • Zakodowane w formacie Base64 i bez opakowania
  • Minimum 16 znaków
  • Może mieć maksymalnie 500 znaków.

Oto kilka typowych sposobów korzystania z pola nonce w interfejsie Play Integrity API. Aby uzyskać najsilniejszą ochronę ze źródła nonce, możesz połączyć poniższe metody.

Dołącz hasz żądania, aby chronić przed manipulacją

Parametru nonce możesz użyć w klasycznym żądaniu do interfejsu API, podobnie jak parametru requestHash w standardowym żądaniu do interfejsu API, aby chronić zawartość żądania przed manipulacją.

Gdy prosisz o ocenę integralności:

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

Gdy otrzymasz ocenę integralności:

  1. Zdekoduj i zweryfikuj token integralności oraz uzyskaj skrót z pola nonce.
  2. Oblicz podsumowanie żądania w taki sam sposób jak w aplikacji (np. z użyciem SHA256 szeregowania żądania).
  3. Porównaj podsumowania po stronie aplikacji i po stronie serwera. Jeśli są inne, żądanie nie jest wiarygodne.

Podaj unikalne wartości, aby chronić się przed atakami typu replay.

Aby uniemożliwić szkodliwym użytkownikom ponowne wykorzystywanie poprzednich odpowiedzi z Play Integrity API, możesz użyć pola nonce do jednoznacznego identyfikowania każdej wiadomości.

Gdy prosisz o ocenę integralności:

  1. uzyskanie unikalnej globalnie wartości w sposób, którego szkodliwi użytkownicy nie są w stanie przewidzieć. Na przykład zabezpieczoną kryptograficznie liczbą losową wygenerowaną po stronie serwera może być taka wartość lub wcześniejszy identyfikator, taki jak identyfikator sesji lub transakcji. Prostszym i mniej bezpiecznym wariantem jest wygenerowanie liczby losowej na urządzeniu. Zalecamy tworzenie wartości o wielkości co najmniej 128 bitów.
  2. Wywołaj setNonce(), aby ustawić w polu nonce unikalną wartość z 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 została użyta po raz pierwszy (serwer musi przechowywać rejestr wygenerowanych wartości przez odpowiedni czas). Jeśli otrzymana unikalna wartość została już użyta lub nie ma jej w rejestrze, odrzuć żądanie
  3. Jeśli unikalna wartość została wygenerowana na urządzeniu, sprawdź, czy otrzymana wartość jest używana po raz pierwszy (serwer musi przechowywać rejestr już widocznych wartości przez odpowiedni czas). Jeśli otrzymana unikalna wartość została już użyta, odrzuć żądanie.

Połącz obie zabezpieczenia przed manipulacją i atakami typu replay (zalecane)

Pole nonce może służyć do ochrony przed nieuprawnionymi modyfikacjami i atakami jednocześnie. Aby to zrobić, wygeneruj unikalną wartość w sposób opisany powyżej i uwzględnij ją w swoim żądaniu. Następnie oblicz hasz żądania, pamiętając o umieszczeniu w nim unikalnej wartości. Oto implementacja łącząca oba te podejścia:

Gdy prosisz o ocenę integralności:

  1. Użytkownik inicjuje działanie o dużej wartości.
  2. Uzyskaj niepowtarzalną wartość dla tego działania zgodnie z opisem w sekcji Uwzględnij unikalne 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 podsumowanie wiadomości, którą chce chronić, zgodnie z opisem w sekcji Dodawanie skrótu żądania w celu ochrony przed próbami wyłudzenia informacji. Wiadomość zawiera unikalną wartość, więc jest ona częścią skrótu.
  5. Użyj operatora setNonce(), aby w polu nonce podać obliczone podsumowanie z poprzedniego kroku.

Gdy otrzymasz ocenę integralności:

  1. Uzyskaj unikalną wartość z żądania
  2. Zdekoduj i zweryfikuj token integralności oraz uzyskaj skrót z pola nonce.
  3. Jak opisano w sekcji Dołącz hasz żądania, aby zabezpieczyć przed manipulacją, ponownie oblicz skrót po stronie serwera i sprawdź, czy pasuje on do skrótu uzyskanego z tokena integralności.
  4. Jak opisano w sekcji Uwzględnij unikalne wartości, aby chronić się przed atakami typu powtórzenie, sprawdź poprawność unikalnej wartości.

Na diagramie sekwencji widać te kroki z elementem nonce po stronie serwera:

Diagram sekwencji pokazujący, jak chronić się
przed manipulacją i powtarzaniem ataków

Poproś o ocenę integralności

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

  1. Utwórz IntegrityManager, jak pokazano w poniższych przykładach.
  2. Utwórz IntegrityTokenRequest, podając nonce za pomocą metody setNonce() w powiązanym kreatorze. Aplikacje rozpowszechniane wyłącznie poza Google Play i pakiety SDK również muszą określać numer projektu Google Cloud za pomocą metody setCloudProjectNumber(). Aplikacje w Google Play są połączone z projektem Cloud w Konsoli Play i nie muszą określać numeru projektu Cloud w żądaniu.
  3. Poproś menedżera o skontaktowanie się z firmą requestIntegrityToken(), podając 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 sprawdź ocenę integralności

Gdy prosisz o ocenę integralności, interfejs Play Integrity API udostępnia token podpisanej odpowiedzi. nonce podany w żądaniu staje się częścią tokena odpowiedzi.

Format tokena

Token jest zagnieżdżonym tokenem sieciowym JSON (JWT) JSON Web Encryption (JWE) JSON Web Signature (JWS). Komponenty JWE i JWS są reprezentowane za pomocą kompaktowej serii.

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

  • JWE używa A256KW dla alg i A256GCM w przypadku enc

  • JWS używa standardu ES256.

Odszyfrowywanie i weryfikacja 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ą. Podczas tego procesu musisz przypisać do konta usługi role Użytkownik kont usługi i Konsument korzystania z usług.
  2. Za pomocą zakresu playintegrity pobierz token dostępu z danych logowania konta usługi na serwerze aplikacji i wyślij to żądanie:

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

Odszyfrowywanie i weryfikację lokalnie

Jeśli zdecydujesz się zarządzać kluczami szyfrowania odpowiedzi i je pobrać, możesz odszyfrować i zweryfikować zwrócony token we własnym bezpiecznym środowisku serwera. Zwrócony token możesz uzyskać, korzystając z metody IntegrityTokenResponse#token().

Poniższy przykład pokazuje, jak zdekodować klucz AES i publiczny klucz EC zakodowany w formacie DER na potrzeby weryfikacji podpisu w Konsoli Play na klucze właściwy dla języka (w tym przypadku w języku 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 następnie zweryfikuj 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 to token tekstowy zawierający oceny integralności.