Wenn Sie nur Standard-API-Anfragen senden möchten, die für die meisten Entwickler geeignet sind, können Sie mit den Integritätsurteilen fortfahren. Auf dieser Seite wird beschrieben, wie Sie klassische API-Anfragen für Integritätsurteile stellen, die unter Android 4.4 (API-Ebene 19) und höher unterstützt werden.
Wissenswertes
Standard- und klassische Anfragen vergleichen
Je nach den Sicherheits- und Missbrauchsanforderungen Ihrer App können Sie Standardanfragen, klassische Anfragen oder eine Kombination aus beiden senden. Standardanfragen sind für alle Apps und Spiele geeignet und können verwendet werden, um zu prüfen, ob eine Aktion oder ein Serveraufruf echt ist. Außerdem wird Google Play ein gewisser Schutz vor Wiederholbarkeit und Datenexfiltration übertragen. Klassische Anfragen sind teurer und Sie sind dafür verantwortlich, sie richtig zu implementieren, um sich vor Datenexfiltration und bestimmten Arten von Angriffen zu schützen. Klassische Anfragen sollten seltener als Standardanfragen gestellt werden, z. B. als gelegentliche einmalige Anfrage, um zu prüfen, ob eine besonders wertvolle oder sensible Aktion echt ist.
In der folgenden Tabelle werden die wichtigsten Unterschiede zwischen den beiden Arten von Anfragen hervorgehoben:
Standard-API-Anfrage | Klassische API-Anfrage | |
---|---|---|
Voraussetzungen | ||
Erforderliche Mindestversion des Android SDK | Android 5.0 (API-Level 21) oder höher | Android 4.4 (API-Level 19) oder höher |
Google Play-Anforderungen | Google Play Store und Google Play-Dienste | Google Play Store und Google Play-Dienste |
Integrationsdetails | ||
API-Vorwärmung erforderlich | ✔️ (einige Sekunden) | ❌ |
Typische Anfragelatenz | Einige hundert Millisekunden | Einige Sekunden |
Potenzielle Anfragehäufigkeit | Häufig (auf Anfrage nach Aktionen oder Anfragen suchen) | Selten (einmalige Prüfung auf Aktionen mit dem höchsten Wert oder die sensibelsten Anfragen) |
Zeitüberschreitungen | Die meisten Warm-ups dauern weniger als 10 Sekunden, beinhalten aber einen Serveraufruf. Daher wird eine lange Zeitüberschreitung empfohlen (z. B. 1 Minute). Urteilsanfragen erfolgen clientseitig | Die meisten Anfragen dauern weniger als 10 Sekunden, beinhalten aber einen Serveraufruf. Daher wird eine lange Zeitüberschreitung empfohlen (z. B. 1 Minute). |
Integritätsurteilstoken | ||
Enthält Geräte-, App- und Kontodetails | ✔️ | ✔️ |
Token-Caching | Geschütztes On-Device-Caching von Google Play | Nicht empfohlen |
Token über den Google Play-Server entschlüsseln und überprüfen | ✔️ | ✔️ |
Typische Latenz bei der Entschlüsselung von Server-zu-Server-Anfragen | 10 Millisekunden bei einer Verfügbarkeit von 99,99 % | 10 Millisekunden bei einer Verfügbarkeit von 99,99 % |
Token lokal in einer sicheren Serverumgebung entschlüsseln und überprüfen | ❌ | ✔️ |
Token clientseitig entschlüsseln und überprüfen | ❌ | ❌ |
Aktualität des Integritätsurteils | Teilweise automatisches Caching und Aktualisieren durch Google Play | Alle Entscheidungen werden bei jeder Anfrage neu berechnet. |
Zeitbeschränkungen | ||
Anfragen pro App und Tag | Standardmäßig 10.000 (eine Erhöhung kann beantragt werden) | Standardmäßig 10.000 (eine Erhöhung kann beantragt werden) |
Anfragen pro App-Instanz und Minute | Aufwärmungen: 5 pro Minute Integritätstokens: Kein öffentliches Limit* |
Integritätstokens: 5 pro Minute |
Schutz | ||
Schutz vor Manipulation und ähnlichen Angriffen | Feld requestHash verwenden |
Feld nonce mit Inhaltsbindung basierend auf Anfragedaten verwenden |
Schutz vor Replay- und ähnlichen Angriffen | Automatische Risikominderung durch Google Play | Feld nonce mit serverseitiger Logik verwenden |
* Alle Anfragen, auch solche ohne öffentliche Limits, unterliegen bei hohen Werten nicht öffentlichen Schutzlimits.
Klassische Anfragen selten stellen
Das Generieren eines Integritätstokens kostet Zeit, Daten und Akku. Außerdem hat jede App eine maximale Anzahl klassischer Anfragen, die sie pro Tag stellen kann. Daher sollten Sie klassische Anfragen nur dann senden, wenn Sie sich zusätzlich zu einer Standardanfrage vergewissern möchten, dass Transaktionen mit dem höchsten Wert oder sensiblen Aktionen echt sind. Sie sollten keine klassischen Anfragen für häufige oder wenig wertvolle Aktionen senden. Senden Sie keine klassischen Anfragen, wenn die App in den Vordergrund wechselt oder alle paar Minuten im Hintergrund. Außerdem sollten Sie vermeiden, gleichzeitig von einer großen Anzahl von Geräten aus aufzurufen. Eine App, die zu viele klassische Anfragen sendet, wird möglicherweise gedrosselt, um Nutzer vor falschen Implementierungen zu schützen.
Ergebnisse nicht im Cache speichern
Das Caching eines Urteils erhöht das Risiko von Angriffen wie Exfiltration und Replay, bei denen ein gültiges Urteil aus einer nicht vertrauenswürdigen Umgebung wiederverwendet wird. Wenn Sie eine klassische Anfrage stellen und sie dann zur späteren Verwendung im Cache speichern möchten, sollten Sie stattdessen eine Standardanfrage auf Abruf ausführen. Bei Standardanfragen wird ein gewisses Caching auf dem Gerät verwendet. Google Play nutzt jedoch zusätzliche Schutzmechanismen, um das Risiko von Replay-Angriffen und Datendiebstahl zu minimieren.
Klassische Anfragen mit dem Nonce-Feld schützen
Die Play Integrity API bietet das Feld nonce
, mit dem Sie Ihre App zusätzlich vor bestimmten Angriffen wie Replay- und Manipulationsangriffen schützen können. Die Play Integrity API gibt den in diesem Feld festgelegten Wert in der signierten Integritätantwort zurück. Folgen Sie der Anleitung zum Generieren von Nonces, um Ihre App vor Angriffen zu schützen.
Klassische Anfragen mit exponentiellem Backoff wiederholen
Umgebungsbedingungen wie eine instabile Internetverbindung oder ein überlastetes Gerät können dazu führen, dass die Integritätsprüfungen des Geräts fehlschlagen. Das kann dazu führen, dass für ein ansonsten vertrauenswürdiges Gerät keine Labels generiert werden. Um diese Szenarien zu vermeiden, sollten Sie eine Wiederholungsoption mit exponentiellem Backoff verwenden.
Übersicht
Wenn der Nutzer eine wichtige Aktion in Ihrer App ausführt, die Sie mit einer Integritätsprüfung schützen möchten, gehen Sie so vor:
- Das serverseitige Backend Ihrer App generiert einen eindeutigen Wert und sendet ihn an die clientseitige Logik. In den verbleibenden Schritten wird diese Logik als „App“ bezeichnet.
- In Ihrer App wird der
nonce
aus dem eindeutigen Wert und dem Inhalt Ihrer Aktion mit hohem Umsatz generiert. Anschließend wird die Play Integrity API aufgerufen undnonce
übergeben. - Ihre App erhält ein signiertes und verschlüsseltes Ergebnis von der Play Integrity API.
- Ihre App übergibt das signierte und verschlüsselte Urteil an das Back-End Ihrer App.
- Das Backend Ihrer App sendet das Urteil an einen Google Play-Server. Der Google Play-Server entschlüsselt und überprüft das Urteil und gibt die Ergebnisse an das Backend Ihrer App zurück.
- Das Back-End Ihrer App entscheidet basierend auf den Signalen in der Tokennutzlast, wie es fortfahren soll.
- Das Backend Ihrer App sendet die Entscheidung an Ihre App.
Nonce generieren
Wenn Sie eine Aktion in Ihrer App mit der Play Integrity API schützen, können Sie mithilfe des Felds nonce
bestimmte Arten von Angriffen abwehren, z. B. Manipulations- und Replay-Angriffe vom Typ „Man-in-the-Middle“ (Mittlerangriff). Die Play Integrity API gibt den Wert zurück, den Sie in diesem Feld in der signierten Antwort zur Integrität festgelegt haben.
Der im Feld nonce
festgelegte Wert muss richtig formatiert sein:
String
- URL-sicher
- Base64-codiert und ohne Umbruch
- Mindestens 16 Zeichen
- Maximal 500 Zeichen
Im Folgenden finden Sie einige gängige Möglichkeiten, das Feld nonce
in der Play Integrity API zu verwenden. Für den bestmöglichen Schutz durch nonce
können Sie die folgenden Methoden kombinieren.
Fügen Sie einen Anfrage-Hash hinzu, um Manipulationen zu verhindern.
Sie können den Parameter nonce
in einer klassischen API-Anfrage ähnlich wie den Parameter requestHash
in einer Standard-API-Anfrage verwenden, um den Inhalt einer Anfrage vor Manipulationen zu schützen.
Wenn Sie ein Integritätsurteil beantragen, gilt Folgendes:
- Berechnen Sie einen Digest aller wichtigen Anfrageparameter (z.B. SHA256 einer stabilen Anfrageserialisierung) aus der Nutzeraktion oder Serveranfrage, die gerade ausgeführt wird.
- Verwenden Sie
setNonce
, um das Feldnonce
auf den Wert der berechneten Zusammenfassung festzulegen.
Wenn Sie ein Integritätsurteil erhalten:
- Entschlüsseln und prüfen Sie das Integritättoken und rufen Sie den Digest aus dem Feld
nonce
ab. - Berechnen Sie einen Digest der Anfrage auf dieselbe Weise wie in der App (z.B. SHA256 einer stabilen Anfrageserialisierung).
- Vergleichen Sie die App- und Server-Digests. Wenn sie nicht übereinstimmen, ist die Anfrage nicht vertrauenswürdig.
Eindeutige Werte angeben, um vor Replay-Angriffen zu schützen
Um zu verhindern, dass böswillige Nutzer frühere Antworten der Integrity API von Google Play wiederverwenden, können Sie mit dem Feld nonce
jede Nachricht eindeutig identifizieren.
Wenn Sie ein Integritätsurteil beantragen, gilt Folgendes:
- Einen global eindeutigen Wert auf eine Weise abrufen, die von böswilligen Nutzern nicht vorhersehbar ist. Ein solcher Wert kann beispielsweise eine kryptografisch sichere Zufallszahl sein, die auf der Serverseite generiert wird, oder eine bereits vorhandene ID wie eine Sitzungs- oder Transaktions-ID. Eine einfachere und weniger sichere Variante ist die Generierung einer Zufallszahl auf dem Gerät. Wir empfehlen, Werte mit mindestens 128 Bit zu erstellen.
- Rufen Sie
setNonce()
auf, um das Feldnonce
auf den eindeutigen Wert aus Schritt 1 festzulegen.
Wenn Sie ein Integritätsurteil erhalten:
- Dekodiere und überprüfe das Integritätstoken und erhalte den eindeutigen Wert aus dem Feld
nonce
. - Wenn der Wert aus Schritt 1 auf dem Server generiert wurde, prüfen Sie, ob der empfangene eindeutige Wert einer der generierten Werte ist und zum ersten Mal verwendet wird. Ihr Server muss die generierten Werte für eine angemessene Dauer speichern. Wenn der empfangene eindeutige Wert bereits verwendet wurde oder nicht im Datensatz enthalten ist, lehnen Sie die Anfrage ab.
- Andernfalls, wenn der eindeutige Wert auf dem Gerät generiert wurde, prüfen Sie, ob der empfangene Wert zum ersten Mal verwendet wird. Ihr Server muss bereits gesehene Werte für eine angemessene Dauer speichern. Wenn der empfangene eindeutige Wert bereits verwendet wurde, lehne die Anfrage ab.
Beide Schutzmaßnahmen gegen Manipulation und Replay-Angriffe kombinieren (empfohlen)
Das Feld nonce
kann gleichzeitig zum Schutz vor Manipulations- und Replay-Angriffen verwendet werden. Erstellen Sie dazu den eindeutigen Wert wie oben beschrieben und fügen Sie ihn in Ihre Anfrage ein. Berechnen Sie dann den Hashwert der Anfrage und achten Sie darauf, den eindeutigen Wert in den Hashwert aufzunehmen. Eine Implementierung, die beide Ansätze kombiniert, sieht so aus:
Wenn Sie ein Integritätsurteil beantragen, gilt Folgendes:
- Der Nutzer initiiert die Aktion mit hohem Umsatzpotenzial.
- Erfassen Sie einen eindeutigen Wert für diese Aktion, wie im Abschnitt Einzigartige Werte angeben, um Replay-Angriffe zu verhindern beschrieben.
- Verfassen Sie eine Nachricht, die Sie schützen möchten. Fügen Sie den eindeutigen Wert aus Schritt 2 in die Nachricht ein.
- Ihre App berechnet einen Digest der Nachricht, die geschützt werden soll, wie im Abschnitt Einen Anfrage-Hash zum Schutz vor Manipulation einfügen beschrieben. Da die Nachricht den eindeutigen Wert enthält, ist dieser Teil des Hashwerts.
- Verwenden Sie
setNonce()
, um das Feldnonce
auf den berechneten Digest aus dem vorherigen Schritt festzulegen.
Wenn Sie ein Integritätsurteil erhalten:
- Eindeutigen Wert aus der Anfrage abrufen
- Entschlüsseln und prüfen Sie das Integritättoken und rufen Sie den Hash aus dem Feld
nonce
ab. - Wie im Abschnitt Einen Anfrage-Hash zum Schutz vor Manipulation einfügen beschrieben, berechnen Sie den Hashwert auf der Serverseite neu und prüfen Sie, ob er mit dem Hashwert übereinstimmt, der aus dem Integritätstoken abgerufen wurde.
- Prüfen Sie wie im Abschnitt Einzigartige Werte angeben, um sich vor Replay-Angriffen zu schützen beschrieben, die Gültigkeit des eindeutigen Werts.
Das folgende Sequenzdiagramm veranschaulicht diese Schritte mit einer serverseitigennonce
:
Integritätsurteil anfordern
Nachdem Sie eine nonce
generiert haben, können Sie bei Google Play ein Urteil zur Integrität anfordern. Gehen Sie dazu so vor:
- Erstellen Sie eine
IntegrityManager
, wie in den folgenden Beispielen gezeigt. - Erstelle eine
IntegrityTokenRequest
und gib dienonce
über die MethodesetNonce()
im zugehörigen Builder an. Bei Apps, die ausschließlich außerhalb von Google Play vertrieben werden, und SDKs muss die Google Cloud-Projektnummer auch über die MethodesetCloudProjectNumber()
angegeben werden. Apps bei Google Play sind mit einem Cloud-Projekt in der Play Console verknüpft. Die Cloud-Projektnummer muss in der Anfrage nicht angegeben werden. Rufe
requestIntegrityToken()
über den Manager auf und gib dabeiIntegrityTokenRequest
an.
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(); }
Unreal Engine
// .h void MyClass::OnRequestIntegrityTokenCompleted( EIntegrityErrorCode ErrorCode, UIntegrityTokenResponse* Response) { // Check the resulting error code. if (ErrorCode == EIntegrityErrorCode::Integrity_NO_ERROR) { // Get the token. FString Token = Response->Token; } } // .cpp void MyClass::RequestIntegrityToken() { // Receive the nonce from the secure server. FString Nonce = ... // Create the Integrity Token Request. FIntegrityTokenRequest Request = { Nonce }; // Create a delegate to bind the callback function. FIntegrityOperationCompletedDelegate Delegate; // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate. Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted); // Initiate the integrity token request, passing the delegate to handle the result. GetGameInstance() ->GetSubsystem<UIntegrityManager>() ->RequestIntegrityToken(Request, Delegate); }
Nativ
/// 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();
Integritätsurteil entschlüsseln und prüfen
Wenn Sie ein Integritätsurteil anfordern, stellt die Play Integrity API ein signiertes Antworttoken bereit. Das nonce
, das Sie in Ihre Anfrage aufnehmen, wird Teil des Antworttokens.
Token format
Das Token ist ein verschachteltes JSON Web Token (JWT), das JSON Web Encryption (JWE) von JSON Web Signature (JWS) ist. Die JWE- und JWS-Komponenten werden mithilfe der kompakten Serialization dargestellt.
Die Verschlüsselungs-/Signaturalgorithmen werden von verschiedenen JWT-Implementierungen gut unterstützt:
Auf den Google-Servern entschlüsseln und überprüfen (empfohlen)
Mit der Play Integrity API können Sie das Integritätsurteil auf den Google-Servern entschlüsseln und überprüfen. Dadurch wird die Sicherheit Ihrer App erhöht. Gehen Sie dazu so vor:
- Erstellen Sie ein Dienstkonto im Google Cloud-Projekt, das mit Ihrer App verknüpft ist.
Rufen Sie auf dem Server Ihrer App das Zugriffstoken aus den Anmeldedaten Ihres Dienstkontos mit dem Gültigkeitsbereich
playintegrity
ab und stellen Sie die folgende Anfrage:playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
Lies die JSON-Antwort.
Lokal entschlüsseln und bestätigen
Wenn Sie Ihre Schlüssel zur Verschlüsselung von Antworten selbst verwalten und herunterladen, können Sie das zurückgegebene Token in Ihrer eigenen sicheren Serverumgebung entschlüsseln und prüfen.
Sie können das zurückgegebene Token mit der Methode IntegrityTokenResponse#token()
abrufen.
Im folgenden Beispiel wird gezeigt, wie der AES-Schlüssel und der DER-codierte öffentliche EC-Schlüssel für die Signaturprüfung aus der Play Console in sprachspezifische Schlüssel (in unserem Fall in der Java-Programmiersprache) im Backend der App decodiert werden. Die Schlüssel sind mit Standard-Flags base64-codiert.
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));
Verwenden Sie diese Schlüssel als Nächstes, um zuerst das Integritätstoken (JWE-Teil) zu entschlüsseln und dann den verschachtelten JWS-Teil zu prüfen und zu extrahieren.
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();
Die resultierende Nutzlast ist ein Klartext-Token, das Integritätsbewertungen enthält.