Klassische API-Anfrage stellen

Wenn Sie nur Standard-API-Anfragen senden möchten, die für die meisten Entwickler geeignet sind, können Sie mit den Integritätsergebnissen fortfahren. Auf dieser Seite wird beschrieben, wie Sie klassische API-Anfragen für Integritätsergebnisse stellen, die unter Android 4.4 (API-Level 19) und höher unterstützt werden.

Wissenswertes

Standard- und klassische Anfragen vergleichen

Du kannst Standardanfragen, klassische Anfragen oder eine Kombination aus beidem senden, je nach den Anforderungen deiner Anwendung in puncto Sicherheit und Missbrauch. 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, während Google Play einen gewissen Schutz vor Wiederholbarkeit und Daten-Exfiltration delegiert. Klassische Anfragen sind teurer in der Einrichtung und Sie sind dafür verantwortlich, sie korrekt zu implementieren, um vor Daten-Exfiltration und bestimmten Angriffen zu schützen. Klassische Anfragen sollten weniger häufig gestellt werden als Standardanfragen, z. B. als gelegentliche einmalige Überprüfung, ob eine besonders wertvolle oder vertrauliche Aktion echt ist.

In der folgenden Tabelle sind die wichtigsten Unterschiede zwischen den beiden Anfragetypen aufgeführt:

Standard-API-Anfrage Klassische API-Anfrage
Voraussetzungen
Android SDK-Mindestversion erforderlich Android 5.0 (API-Level 21) oder höher Android 4.4 (API-Level 19) oder höher
Anforderungen an Google Play Google Play Store und Google Play-Dienste Google Play Store und Google Play-Dienste
Details zur Integration
API-Aufwärmphase erforderlich ✔️ (ein paar Sekunden)
Typische Anfragelatenz Einige Hundert Millisekunden Einige Sekunden
Potenzielle Anfragehäufigkeit Häufig (On-Demand-Prüfung für jede Aktion oder Anfrage) Selten (einmalige Prüfung von Aktionen mit dem höchsten Wert oder besonders sensiblen Anfragen)
Vorübergehende Blockierungen Die meisten Aufwärmphasen dauern unter 10 Sekunden, erfordern jedoch einen Serveraufruf. Daher wird ein langes Zeitlimit empfohlen (z. B. 1 Minute). Ergebnisanfragen erfolgen clientseitig Die meisten Anfragen dauern weniger als 10 Sekunden, erfordern jedoch einen Serveraufruf. Daher wird ein langes Zeitlimit empfohlen (z. B. 1 Minute).
Token für Integritätsergebnis
Enthält Geräte-, App- und Kontodetails ✔️ ✔️
Token-Caching Geschütztes Caching auf dem Gerät durch Google Play Nicht empfohlen
Token über den Google Play-Server entschlüsseln und verifizieren ✔️ ✔️
Typische Latenz von Server-zu-Server-Entschlüsselungsanfragen 10 Sekunden von Millisekunden mit einer Verfügbarkeit von 3 Neunen 10 Sekunden von Millisekunden mit einer Verfügbarkeit von 3 Neunen
Token lokal in einer sicheren Serverumgebung entschlüsseln und prüfen ✔️
Token clientseitig entschlüsseln und überprüfen
Aktualität des Integritätsergebnisses Einige automatische Caching- und Aktualisierungsfunktionen von Google Play Alle Ergebnisse werden für jede 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 Anwendungsinstanz und Minute Warm-ups: 5 pro Minute
Integritätstokens: Kein öffentliches Limit*
Integritätstokens: 5 pro Minute
Schutz
Manipulation und ähnlichen Angriffen verhindern Feld „requestHash“ verwenden Feld nonce mit Inhaltsbindung basierend auf Anfragedaten verwenden
Abwehr von Replay-Angriffen und ähnlichen Angriffen Automatische Risikominderung von Google Play Feld nonce mit serverseitiger Logik verwenden

* Alle Anfragen, auch solche ohne öffentliche Beschränkungen, unterliegen bei hohen Werten nicht öffentlichen Verteidigungsgrenzen.

Klassische Anfragen selten stellen

Beim Generieren eines Integritätstokens werden Zeit, Daten und der Akkuverbrauch verbraucht. Außerdem hat jede Anwendung eine maximale Anzahl klassischer Anfragen, die sie pro Tag stellen kann. Daher sollten Sie klassische Anfragen nur stellen, um den höchsten Wert zu prüfen. Vertrauliche Aktionen sind echt, wenn Sie eine zusätzliche Garantie für eine Standardanfrage wünschen. Für Aktionen mit hoher Häufigkeit oder mit geringem Wert sollten keine klassischen Anfragen gesendet werden. Stellen Sie nicht jedes Mal klassische Anfragen, wenn die App in den Vordergrund oder alle paar Minuten im Hintergrund ausgeführt wird, und vermeiden Sie Anrufe von einer großen Anzahl von Geräten gleichzeitig. Eine Anwendung, die zu viele klassische Anfragen sendet, kann gedrosselt werden, um Nutzer vor fehlerhaften Implementierungen zu schützen.

Ergebnisse im Cache vermeiden

Das Caching eines Ergebnisses erhöht das Risiko von Angriffen wie Exfiltration und Replay-Angriffen, bei denen ein gutes Ergebnis aus einer nicht vertrauenswürdigen Umgebung wiederverwendet wird. Wenn Sie erwägen, eine klassische Anfrage zu senden und diese dann zur späteren Verwendung im Cache zu speichern, wird stattdessen empfohlen, bei Bedarf eine Standardanfrage auszuführen. Bei Standardanfragen wird das Gerät in gewissem Umfang zwischengespeichert. Google Play setzt jedoch zusätzliche Schutztechniken ein, um das Risiko von Replay-Angriffen und Daten-Exfiltration zu minimieren.

Mit dem Nonce-Feld klassische Anfragen schützen

Die Play Integrity API bietet ein Feld namens nonce, mit dem Ihre App noch vor bestimmten Angriffen wie Replay- und Manipulationsangriffen geschützt werden kann. Die Play Integrity API gibt den Wert zurück, den Sie in diesem Feld in der signierten Integritätsantwort festgelegt haben. 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 Geräteintegritätsprüfungen fehlschlagen. Dies kann dazu führen, dass keine Labels für ein Gerät generiert werden, das ansonsten vertrauenswürdig ist. Um diese Szenarien abzuschwächen, fügen Sie eine Wiederholungsoption mit exponentiellem Backoff ein.

Übersicht

Sequenzdiagramm, das das allgemeine Design der Play Integrity API zeigt

Wenn der Nutzer in Ihrer App eine wichtige Aktion ausführt, die Sie mit einer Integritätsprüfung schützen möchten, führen Sie die folgenden Schritte aus:

  1. Das serverseitige Back-End Ihrer Anwendung generiert einen eindeutigen Wert und sendet ihn an die clientseitige Logik. In den restlichen Schritten wird diese Logik als Ihre „App“ bezeichnet.
  2. Ihre App erstellt die nonce aus dem eindeutigen Wert und dem Inhalt Ihrer hochwertigen Aktion. Anschließend wird die Play Integrity API aufgerufen und die nonce übergeben.
  3. Deine App erhält ein signiertes und verschlüsseltes Ergebnis von der Play Integrity API.
  4. Deine App übergibt das signierte und verschlüsselte Ergebnis an das Backend deiner App.
  5. Das Backend deiner App sendet das Ergebnis an einen Google Play-Server. Der Google Play-Server entschlüsselt und überprüft das Ergebnis und gibt die Ergebnisse an das Back-End Ihrer Anwendung zurück.
  6. Das Back-End Ihrer Anwendung entscheidet anhand der Signale in der Tokennutzlast, wie fortfahren soll.
  7. Das Back-End Ihrer App sendet die Entscheidungsergebnisse an Ihre App.

Nonce generieren

Wenn Sie eine Aktion in Ihrer App mit der Play Integrity API schützen, können Sie das Feld nonce nutzen, um bestimmte Arten von Angriffen abzuschwächen, z. B. Person-in-the-Middle-(PITM)-Manipulationsangriffe und Replay-Angriffe. Die Play Integrity API gibt den Wert zurück, den Sie in diesem Feld in der signierten Integritätsantwort festgelegt haben.

Der im Feld nonce festgelegte Wert muss richtig formatiert sein:

  • String
  • URL-sicher
  • Als Base64 codiert und ohne Wrapping
  • 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 vor nonce können Sie die folgenden Methoden kombinieren.

Anfrage-Hash zum Schutz vor Manipulation hinzufügen

Sie können den nonce-Parameter in einer klassischen API-Anfrage ähnlich wie den requestHash-Parameter in einer Standard-API-Anfrage verwenden, um den Inhalt einer Anfrage vor Manipulationen zu schützen.

Wenn Sie ein Integritätsergebnis anfordern, geschieht Folgendes:

  1. Berechnen Sie einen Digest aller kritischen Anfrageparameter (z.B. SHA256 einer stabilen Anfrageserialisierung) aus der Nutzeraktion oder Serveranfrage.
  2. Verwenden Sie setNonce, um das Feld nonce auf den Wert des berechneten Digests festzulegen.

Wenn du ein Integritätsergebnis erhältst:

  1. Decodieren und prüfen Sie das Integritätstoken und rufen Sie den Digest aus dem Feld nonce ab.
  2. Berechnet einen Digest der Anfrage auf dieselbe Weise wie in der Anwendung (z.B. SHA256 einer stabilen Anfrageserialisierung).
  3. Vergleichen Sie die App- und serverseitigen Digests. Stimmen sie nicht überein, ist die Anfrage nicht vertrauenswürdig.

Geben Sie eindeutige Werte zum Schutz vor Replay-Angriffen an

Um zu verhindern, dass böswillige Nutzer frühere Antworten der Play Integrity API wiederverwenden, können Sie jede Nachricht mithilfe des Felds nonce eindeutig identifizieren.

Wenn Sie ein Integritätsergebnis anfordern, geschieht Folgendes:

  1. Einen global eindeutigen Wert auf eine Weise erhalten, die böswillige Benutzer nicht vorhersagen können. Bei einer auf der Serverseite generierten kryptografisch sicheren Zufallszahl kann es sich beispielsweise um einen solchen Wert oder eine bereits vorhandene ID handeln, z. B. eine Sitzung oder eine Transaktions-ID. Eine einfachere und weniger sichere Variante besteht darin, eine Zufallszahl auf dem Gerät zu generieren. Wir empfehlen, Werte mit einer Mindestgröße von 128 Bit zu erstellen.
  2. Rufen Sie setNonce() auf, um das Feld nonce auf den eindeutigen Wert aus Schritt 1 festzulegen.

Wenn du ein Integritätsergebnis erhältst:

  1. Decodieren und prüfen Sie das Integritätstoken und rufen Sie den eindeutigen Wert aus dem Feld nonce ab.
  2. 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 ob er zum ersten Mal verwendet wird. Ihr Server muss einen Datensatz der generierten Werte für eine angemessene Dauer aufbewahren. Wenn der empfangene eindeutige Wert bereits verwendet wurde oder nicht im Eintrag erscheint, lehnen Sie die Anfrage ab.
  3. 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 einen Datensatz der bereits erkannten Werte für eine angemessene Dauer aufbewahren. Wenn der empfangene eindeutige Wert bereits verwendet wurde, lehnen Sie die Anfrage ab.

Beide Schutzmechanismen gegen Manipulations- und Replay-Angriffe kombinieren (empfohlen)

Das Feld nonce kann gleichzeitig vor Manipulations- und Replay-Angriffen verwendet werden. Generieren Sie dazu den eindeutigen Wert wie oben beschrieben und fügen Sie ihn in Ihre Anfrage ein. Berechnen Sie dann den Anfrage-Hash und achten Sie darauf, dass der eindeutige Wert Teil des Hashs ist. Eine Implementierung, bei der beide Ansätze kombiniert werden, sieht so aus:

Wenn Sie ein Integritätsergebnis anfordern, geschieht Folgendes:

  1. Der Nutzer initiiert die Aktion mit hohem Wert.
  2. Rufen Sie einen eindeutigen Wert für diese Aktion ab, wie im Abschnitt Eindeutige Werte zum Schutz vor Replay-Angriffen einschließen beschrieben.
  3. Bereiten Sie eine Nachricht vor, die Sie schützen möchten. Geben Sie dabei den eindeutigen Wert aus Schritt 2 an.
  4. Ihre Anwendung berechnet einen Digest der Nachricht, die sie schützen möchte, wie im Abschnitt Anfrage-Hash zum Schutz vor Manipulation hinzufügen beschrieben. Da die Nachricht den eindeutigen Wert enthält, ist dieser Teil des Hashwerts.
  5. Verwenden Sie setNonce(), um das Feld nonce auf den berechneten Digest aus dem vorherigen Schritt festzulegen.

Wenn du ein Integritätsergebnis erhältst:

  1. Eindeutigen Wert aus der Anfrage abrufen
  2. Decodieren und prüfen Sie das Integritätstoken und rufen Sie den Digest aus dem Feld nonce ab.
  3. Wie im Abschnitt Anfrage-Hash zum Schutz vor Manipulationen hinzufügen beschrieben, berechnen Sie den Digest auf Serverseite neu und prüfen Sie, ob er mit dem Digest aus dem Integritätstoken übereinstimmt.
  4. Prüfen Sie wie im Abschnitt Einzelne Werte zum Schutz vor Wiederholungsangriffen einbeziehen die Gültigkeit des eindeutigen Werts.

Das folgende Sequenzdiagramm veranschaulicht diese Schritte mit einem serverseitigen nonce:

Sequenzdiagramm, das zeigt, wie man vor Manipulations- und Wiederholungsangriffen schützt

Integritätsergebnis anfordern

Nachdem du ein nonce generiert hast, kannst du ein Integritätsergebnis von Google Play anfordern. Führen Sie dazu die folgenden Schritte aus:

  1. Erstellen Sie ein IntegrityManager, wie in den folgenden Beispielen gezeigt.
  2. Erstellen Sie ein IntegrityTokenRequest und geben Sie dabei nonce über die Methode setNonce() im zugehörigen Builder an. Apps, die ausschließlich außerhalb von Google Play und SDKs vertrieben werden, müssen außerdem ihre Google Cloud-Projektnummer über die Methode setCloudProjectNumber() angeben. Apps bei Google Play sind mit einem Cloud-Projekt in der Play Console verknüpft und müssen in der Anfrage nicht die Cloud-Projektnummer festgelegt werden.
  3. Rufen Sie über das Manager requestIntegrityToken() auf und geben Sie das IntegrityTokenRequest 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();
}

Native Anzeige

/// 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ätsergebnis entschlüsseln und prüfen

Wenn Sie ein Integritätsergebnis anfordern, stellt die Play Integrity API ein signiertes Antworttoken bereit. Der nonce in Ihrer Anfrage wird Teil des Antworttokens.

Token format

Das Token ist ein verschachteltes JSON Web Token (JWT), das die JSON Web Encryption (JWE) der JSON-Websignatur (JWS) ist. Die JWE- und JWS-Komponenten werden mithilfe von kompakter Serialisierung dargestellt.

Die Verschlüsselungs-/Signaturalgorithmen werden in verschiedenen JWT-Implementierungen gut unterstützt:

  • JWE verwendet A256KW für alg und A256GCM für enc

  • JWS verwendet ES256.

Auf Google-Servern entschlüsseln und prüfen (empfohlen)

Mit der Play Integrity API kannst du das Integritätsergebnis auf den Google-Servern entschlüsseln und prüfen, um die Sicherheit deiner App zu verbessern. Führe dazu die folgenden Schritte aus:

  1. Erstellen Sie ein Dienstkonto innerhalb des Google Cloud-Projekts, das mit Ihrer Anwendung verknüpft ist. Bei der Kontoerstellung müssen Sie dem Dienstkonto die Rollen Service Account User (Dienstkontonutzer) und Service Usage-Nutzer zuweisen.
  2. Rufen Sie auf dem Anwendungsserver mit dem Bereich playintegrity das Zugriffstoken aus den Anmeldedaten Ihres Dienstkontos ab und stellen Sie die folgende Anfrage:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Lesen Sie die JSON-Antwort.

Lokal entschlüsseln und prüfen

Wenn Sie Ihre Schlüssel zur Verschlüsselung von Antworten 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.

Das folgende Beispiel zeigt, wie der AES-Schlüssel und der DER-codierte öffentliche EC-Schlüssel für die Signaturprüfung von der Play Console in sprachspezifische Schlüssel (in unserem Fall die Programmiersprache Java) im Back-End 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 Nur-Text-Token, das Integritätsergebnisse enthält.