Na tej stronie opisaliśmy wysyłanie standardowych żądań interfejsu API w celu uzyskania werdyktu integralności, które są obsługiwane w Androidzie 5.0 (poziom interfejsu API 21) i nowszych. Aby sprawdzić, czy interakcja jest autentyczna, możesz wysłać standardowe żądanie interfejsu API dotyczące oceny integralności, gdy aplikacja wysyła żądanie do serwera.
Omówienie
Standardowe żądanie składa się z 2 części:
- Przygotowanie dostawcy tokena integralności (jednorazowo): musisz wywołać interfejs API Integrity, aby przygotować dostawcę tokena integralności na długo przed uzyskaniem oceny integralności. Możesz to zrobić na przykład podczas uruchamiania aplikacji lub w tle, zanim będzie potrzebne orzeczenie dotyczące integralności.
- Poproś o token integralności (na żądanie): gdy aplikacja wysyła żądanie serwera, którego autentyczność chcesz sprawdzić, żądasz tokena integralności i wysyłasz go do serwera backendu aplikacji w celu odszyfrowania i weryfikacji. Następnie serwer backendu może podjąć odpowiednie działania.
Przygotuj dostawcę tokena integralności (jednorazowo):
- Aplikacja wywołuje dostawcę tokenu integralności z numerem projektu Google Cloud.
- Aplikacja przechowuje w pamięci dostawcę tokena integralności na potrzeby dalszych wywołań funkcji weryfikacji.
Poproś o token integralności (na żądanie):
- W przypadku działania użytkownika, które wymaga ochrony, aplikacja oblicza szyfr (za pomocą dowolnego odpowiedniego algorytmu szyfrowania, np. SHA256) dla danego żądania.
- Aplikacja prosi o token integralności, przekazując hasz żądania.
- Twoja aplikacja otrzymuje podpisany i zaszyfrowany token integralności od interfejsu Play Integrity API.
- Aplikacja przekazuje token integralności do backendu.
- Serwer backendu aplikacji wysyła token na serwer Google Play. Serwer Google Play odszyfrowuje i weryfikuje werdykt, a potem zwraca wyniki do backendu aplikacji.
- Backend aplikacji decyduje, jak postępować, na podstawie sygnałów zawartych w ładunku tokena.
- Backend aplikacji wysyła do niej wyniki decyzji.
Przygotuj dostawcę tokena integralności (jednorazowo)
Zanim prześlesz standardowe żądanie oceny integralności z Google Play, musisz przygotować (czyli „rozgrzać”) dostawcę tokena integralności. Dzięki temu Google Play może inteligentnie przechowywać w pamięci podręcznej na urządzeniu informacje o częściowym uwierzytelnieniu, aby zmniejszyć opóźnienie na ścieżce krytycznej, gdy wysyłasz żądanie dotyczące orzeczenia o integralności. Przygotowanie dostawcy tokenów to sposób na powtórzenie weryfikacji integralności, które nie wymagają dużych zasobów. Dzięki temu następny wynik weryfikacji integralności będzie bardziej aktualny.
Dostawcę tokena integralności możesz przygotować:
- podczas uruchamiania aplikacji (czyli w przypadku uruchomienia „na zimno”). Przygotowanie dostawcy tokenów odbywa się asynchronicznie, więc nie wpłynie na czas uruchamiania. Ta opcja sprawdzi się, jeśli planujesz wysłać prośbę o ocenę integralności zaraz po uruchomieniu aplikacji, np. gdy użytkownik zaloguje się lub gracz dołączy do gry.
- podczas otwierania aplikacji (czyli przy uruchomieniu z pamięci); Pamiętaj jednak, że każda instancja aplikacji może przygotować token integralności tylko do 5 razy na minutę.
- W dowolnym momencie w tle, gdy chcesz przygotować token z wyprzedzeniem w ramach prośby o orzeczenie dotyczące integralności.
Aby przygotować dostawcę tokenów integralności, wykonaj te czynności:
- Utwórz
StandardIntegrityManager
, jak pokazano w tych przykładach. - Utwórz obiekt
PrepareIntegrityTokenRequest
, podając numer projektu Google Cloud za pomocą metodysetCloudProjectNumber()
. - Zadzwoń do
prepareIntegrityToken()
za pomocą menedżera, podającPrepareIntegrityTokenRequest
.
Java
import com.google.android.gms.tasks.Task; // Create an instance of a manager. StandardIntegrityManager standardIntegrityManager = IntegrityManagerFactory.createStandard(applicationContext); StandardIntegrityTokenProvider integrityTokenProvider; long cloudProjectNumber = ...; // Prepare integrity token. Can be called once in a while to keep internal // state fresh. standardIntegrityManager.prepareIntegrityToken( PrepareIntegrityTokenRequest.builder() .setCloudProjectNumber(cloudProjectNumber) .build()) .addOnSuccessListener(tokenProvider -> { integrityTokenProvider = tokenProvider; }) .addOnFailureListener(exception -> handleError(exception));
Unity
IEnumerator PrepareIntegrityTokenCoroutine() { long cloudProjectNumber = ...; // Create an instance of a standard integrity manager. var standardIntegrityManager = new StandardIntegrityManager(); // Request the token provider. var integrityTokenProviderOperation = standardIntegrityManager.PrepareIntegrityToken( new PrepareIntegrityTokenRequest(cloudProjectNumber)); // Wait for PlayAsyncOperation to complete. yield return integrityTokenProviderOperation; // Check the resulting error code. if (integrityTokenProviderOperation.Error != StandardIntegrityErrorCode.NoError) { AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " + integrityTokenProviderOperation.Error); yield break; } // Get the response. var integrityTokenProvider = integrityTokenProviderOperation.GetResult(); }
Unreal Engine
// .h void MyClass::OnPrepareIntegrityTokenCompleted( EStandardIntegrityErrorCode ErrorCode, UStandardIntegrityTokenProvider* Provider) { // Check the resulting error code. if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR) { // ... } } // .cpp void MyClass::PrepareIntegrityToken() { int64 CloudProjectNumber = ... // Create the Integrity Token Request. FPrepareIntegrityTokenRequest Request = { CloudProjectNumber }; // Create a delegate to bind the callback function. FPrepareIntegrityOperationCompletedDelegate Delegate; // Bind the completion handler (OnPrepareIntegrityTokenCompleted) to the delegate. Delegate.BindDynamic(this, &MyClass::OnPrepareIntegrityTokenCompleted); // Initiate the prepare integrity token operation, passing the delegate to handle the result. GetGameInstance() ->GetSubsystem<UStandardIntegrityManager>() ->PrepareIntegrityToken(Request, Delegate); }
Rodzimy użytkownik
/// Initialize StandardIntegrityManager StandardIntegrityManager_init(/* app's java vm */, /* an android context */); /// Create a PrepareIntegrityTokenRequest opaque object. int64_t cloudProjectNumber = ...; PrepareIntegrityTokenRequest* tokenProviderRequest; PrepareIntegrityTokenRequest_create(&tokenProviderRequest); PrepareIntegrityTokenRequest_setCloudProjectNumber(tokenProviderRequest, cloudProjectNumber); /// Prepare a StandardIntegrityTokenProvider opaque type pointer and call /// StandardIntegrityManager_prepareIntegrityToken(). StandardIntegrityTokenProvider* tokenProvider; StandardIntegrityErrorCode error_code = StandardIntegrityManager_prepareIntegrityToken(tokenProviderRequest, &tokenProvider); /// ... /// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR if (error_code != STANDARD_INTEGRITY_NO_ERROR) { /// Remember to call the *_destroy() functions. return; } /// ... /// Use polling to wait for the async operation to complete. IntegrityResponseStatus token_provider_status; /// Check for error codes. StandardIntegrityErrorCode error_code = StandardIntegrityTokenProvider_getStatus(tokenProvider, &token_provider_status); if (error_code == STANDARD_INTEGRITY_NO_ERROR && token_provider_status == INTEGRITY_RESPONSE_COMPLETED) { /// continue to request token from the token provider } /// ... /// Remember to free up resources. PrepareIntegrityTokenRequest_destroy(tokenProviderRequest);
Ochrona żądań przed modyfikacją (zalecane)
Podczas sprawdzania działania użytkownika w aplikacji za pomocą interfejsu Play Integrity API możesz wykorzystać pole requestHash
, aby ograniczyć ryzyko ataków polegających na modyfikacji aplikacji. Na przykład gra może chcieć przekazać wynik gracza do serwera backendowego, a Twój serwer chce się upewnić, że wynik ten nie został zmieniony przez serwer proxy. Interfejs Play Integrity API zwraca wartość ustawioną w polu requestHash
w ramach podpisanej odpowiedzi interfejsu Integrity API. Bez tego parametru token integralności będzie powiązany tylko z urządzeniem, a nie z konkretnym żądaniem, co stwarza możliwość ataku.requestHash
Z tych instrukcji dowiesz się, jak skutecznie korzystać z pola requestHash
:
Gdy żądasz oceny integralności:
- Oblicz digest wszystkich odpowiednich parametrów żądania (np. skrót SHA256 stabilnej serializacji żądania) na podstawie działania użytkownika lub żądania serwera, które jest realizowane. Wartość ustawiona w polu
requestHash
może mieć maksymalnie 500 bajtów. W polurequestHash
podaj wszelkie dane żądania aplikacji, które są kluczowe lub istotne dla działania, które sprawdzasz lub chronisz. PolerequestHash
jest dołączane do tokena integralności w postaci dosłownej, więc długie wartości mogą zwiększać rozmiar żądania. - Prześlij treść skrótu jako wartość pola
requestHash
w interfejsie Play Integrity API i pobierz token integralności.
Gdy otrzymasz ocenę integralności:
- Odkoduj token integralności i wyodrębnij pole
requestHash
. - Obliczać skrót żądania w taki sam sposób jak w aplikacji (np. SHA256 z użyciem stabilnej serializacji żądania).
- Porównaj skróty po stronie aplikacji i po stronie serwera. Jeśli się różnią, prośba nie jest wiarygodna.
Prośba o ocenę integralności (na żądanie)
Gdy przygotujesz dostawcę tokenów integralności, możesz zacząć żądać oceny integralności z Google Play. Aby to zrobić:
- Uzyskaj
StandardIntegrityTokenProvider
. - Utwórz
StandardIntegrityTokenRequest
, podając hasz żądania działania użytkownika, które chcesz chronić za pomocą metodysetRequestHash
. - Użyj dostawcy tokena integralności, aby wywołać funkcję
request()
, podając parametrStandardIntegrityTokenRequest
.
Java
import com.google.android.gms.tasks.Task; StandardIntegrityTokenProvider integrityTokenProvider; // See above how to prepare integrityTokenProvider. // Request integrity token by providing a user action request hash. Can be called // several times for different user actions. String requestHash = "2cp24z..."; Task<StandardIntegrityToken> integrityTokenResponse = integrityTokenProvider.request( StandardIntegrityTokenRequest.builder() .setRequestHash(requestHash) .build()); integrityTokenResponse .addOnSuccessListener(response -> sendToServer(response.token())) .addOnFailureListener(exception -> handleError(exception));
Unity
IEnumerator RequestIntegrityTokenCoroutine() { StandardIntegrityTokenProvider integrityTokenProvider; // See above how to prepare integrityTokenProvider. // Request integrity token by providing a user action request hash. Can be called // several times for different user actions. String requestHash = "2cp24z..."; var integrityTokenOperation = integrityTokenProvider.Request( new StandardIntegrityTokenRequest(requestHash) ); // Wait for PlayAsyncOperation to complete. yield return integrityTokenOperation; // Check the resulting error code. if (integrityTokenOperation.Error != StandardIntegrityErrorCode.NoError) { AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " + integrityTokenOperation.Error); yield break; } // Get the response. var integrityToken = integrityTokenOperation.GetResult(); }
Unreal Engine
// .h void MyClass::OnRequestIntegrityTokenCompleted( EStandardIntegrityErrorCode ErrorCode, UStandardIntegrityToken* Response) { // Check the resulting error code. if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR) { // Get the token. FString Token = Response->Token; } } // .cpp void MyClass::RequestIntegrityToken() { UStandardIntegrityTokenProvider* Provider = ... // Prepare the UStandardIntegrityTokenProvider. // Request integrity token by providing a user action request hash. Can be called // several times for different user actions. FString RequestHash = ...; FStandardIntegrityTokenRequest Request = { RequestHash }; // Create a delegate to bind the callback function. FStandardIntegrityOperationCompletedDelegate Delegate; // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate. Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted); // Initiate the standard integrity token request, passing the delegate to handle the result. Provider->Request(Request, Delegate); }
Rodzimy użytkownik
/// Create a StandardIntegrityTokenRequest opaque object. const char* requestHash = ...; StandardIntegrityTokenRequest* tokenRequest; StandardIntegrityTokenRequest_create(&tokenRequest); StandardIntegrityTokenRequest_setRequestHash(tokenRequest, requestHash); /// Prepare a StandardIntegrityToken opaque type pointer and call /// StandardIntegrityTokenProvider_request(). Can be called several times for /// different user actions. See above how to prepare token provider. StandardIntegrityToken* token; StandardIntegrityErrorCode error_code = StandardIntegrityTokenProvider_request(tokenProvider, tokenRequest, &token); /// ... /// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR if (error_code != STANDARD_INTEGRITY_NO_ERROR) { /// Remember to call the *_destroy() functions. return; } /// ... /// Use polling to wait for the async operation to complete. IntegrityResponseStatus token_status; /// Check for error codes. StandardIntegrityErrorCode error_code = StandardIntegrityToken_getStatus(token, &token_status); if (error_code == STANDARD_INTEGRITY_NO_ERROR && token_status == INTEGRITY_RESPONSE_COMPLETED) { const char* integrityToken = StandardIntegrityToken_getToken(token); } /// ... /// Remember to free up resources. StandardIntegrityTokenRequest_destroy(tokenRequest); StandardIntegrityToken_destroy(token); StandardIntegrityTokenProvider_destroy(tokenProvider); StandardIntegrityManager_destroy();
Jeśli aplikacja przez zbyt długi czas korzysta z tego samego dostawcy tokenów, może on wygasnąć, co spowoduje błąd INTEGRITY_TOKEN_PROVIDER_INVALID podczas następnego żądania tokena. Aby rozwiązać ten problem, poproś o utworzenie nowego dostawcy.
Odszyfruj i sprawdź ocenę integralności
Po wysłaniu żądania o ocenę integralności interfejs Play Integrity API udostępnia zaszyfrowany token odpowiedzi. Aby uzyskać oceny integralności urządzenia, musisz odszyfrować token integralności na serwerach Google. Aby to zrobić, wykonaj te czynności:
- Utwórz konto usługi w projekcie Google Cloud połączonym z Twoją aplikacją.
Na serwerze aplikacji pobierz token dostępu z uprawnień konta usługi za pomocą zakresu playintegrity i wyślij to żądanie:
playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
Przeczytaj odpowiedź w formacie JSON.
Wynikiem jest token w postaci zwykłego tekstu, który zawiera werdykty dotyczące integralności.
Automatyczna ochrona przed odtwarzaniem
Aby zapobiec atakom polegającym na odtwarzaniu, Google Play automatycznie uniemożliwia wielokrotne używanie tokenów integralności. Próba wielokrotnego odszyfrowania tego samego tokena spowoduje, że wyroki zostaną uznane za czyste:
- Wynik rozpoznawania urządzenia będzie pusty.
- Ocena rozpoznawania aplikacji i ocena licencji aplikacji zostaną ustawione na
UNEVALUATED
. - Wszystkie opcjonalne werdykty włączone w Konsoli Play zostaną ustawione na
UNEVALUATED
(lub na pusty werdykt, jeśli jest to werdykt wielowartościowy).