Klassische API-Anfrage stellen

Wenn Sie nur API-Standardanfragen 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 zu Integritätsergebnissen stellen, die ab Android 4.4 (API-Level 19) unterstützt werden.

Wissenswertes

Standardmäßige und klassische Anfragen vergleichen

Je nach den Anforderungen Ihrer Anwendung an Sicherheit und Missbrauch können Sie Standardanfragen, klassische Anfragen oder eine Kombination aus beidem senden. Standardanfragen sind für alle Apps und Spiele geeignet und können verwendet werden, um zu prüfen, ob Aktionen oder Serveraufrufe echt sind. Gleichzeitig wird ein gewisser Schutz vor Wiederholbarkeit und Daten-Exfiltration an Google Play delegiert. Klassische Anfragen sind teurer und Sie sind dafür verantwortlich, sie korrekt zu implementieren, um sie vor Exfiltration und bestimmten Arten von Angriffen zu schützen. Klassische Anfragen sollten seltener gestellt werden als Standardanfragen, z. B. gelegentlich einmalig, um zu prüfen, ob eine sehr wertvolle oder sensible Aktion echt ist.

In der folgenden Tabelle sind die wichtigsten Unterschiede zwischen den beiden Arten von Anfragen 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
Google Play-Anforderungen Google Play Store und Google Play-Dienste Google Play Store und Google Play-Dienste
Integrationsdetails
API-Aufwärmphase erforderlich ✔️ (einige Sekunden) ❌ Vorstellung
Typische Anfragelatenz Einige hundert Millisekunden Ein paar Sekunden
Mögliche Häufigkeit der Anfragen Häufig (On-Demand-Prüfung für Aktionen oder Anfragen) Selten (einmalige Prüfung auf Aktionen mit dem höchsten Wert oder auf die meisten sensiblen Anfragen)
Vorübergehende Blockierungen Die Aufwärmphase beträgt in der Regel weniger als 10 Sekunden, beinhaltet jedoch einen Serveraufruf. Daher wird ein langes Zeitlimit empfohlen (z. B. 1 Minute). Ergebnisanfragen erfolgen clientseitig Die meisten Anfragen dauern weniger als 10 Sekunden, sie beinhalten jedoch einen Serveraufruf, daher wird ein langes Zeitlimit empfohlen (z. B. 1 Minute).
Integritätsergebnis-Token
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 bestätigen ✔️ ✔️
Typische Server-zu-Server-Anfragelatenz bei der Entschlüsselung 10 Millisekunden mit Verfügbarkeit über 3 Neunen 10 Millisekunden mit Verfügbarkeit über 3 Neunen
Token lokal in einer sicheren Serverumgebung entschlüsseln und verifizieren ❌ Vorstellung ✔️
Token clientseitig entschlüsseln und prüfen ❌ Vorstellung ❌ Vorstellung
Aktualität des Integritätsergebnisses Einige automatische Caching- und Aktualisierungsvorgänge von Google Play Neuberechnung aller Ergebnisse für jede Anfrage
Zeitbeschränkungen
Anfragen pro App und Tag Standardmäßig 10.000 (eine Erhöhung kann angefordert werden) Standardmäßig 10.000 (eine Erhöhung kann angefordert werden)
Anfragen pro Anwendungsinstanz und Minute Aufwärmphase: 5 pro Minute
Integritätstokens: Kein öffentliches Limit*
Integritätstokens: 5 pro Minute
Schutz
Manipulation und ähnliche Angriffe abwehren Feld „requestHash“ verwenden Feld nonce mit Inhaltsbindung basierend auf Anfragedaten verwenden
Abwehren von Replay- und ähnlichen Angriffen Automatische Schadensminderung durch Google Play Feld nonce mit serverseitiger Logik verwenden

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

Klassische Anfragen selten senden

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

Caching-Ergebnisse vermeiden

Das Caching eines Ergebnisses erhöht das Risiko von Angriffen wie Daten-Exfiltration und Replay-Angriffen, bei denen ein gutes Ergebnis aus einer nicht vertrauenswürdigen Umgebung wiederverwendet wird. Wenn Sie eine klassische Anfrage stellen und dann zur späteren Verwendung im Cache speichern möchten, empfiehlt es sich, stattdessen eine Standardanfrage bei Bedarf auszuführen. Bei Standardanfragen wird auf dem Gerät Caching verwendet. Google Play verwendet jedoch zusätzliche Schutzmaßnahmen, um das Risiko von Replay-Angriffen und Daten-Exfiltration zu verringern.

Mit dem Nonce-Feld klassische Anfragen schützen

Die Play Integrity API bietet ein Feld namens nonce, mit dem deine App noch besser vor bestimmten Angriffen wie Wiederholungs- und Manipulationsangriffen geschützt werden kann. Die Play Integrity API gibt den in diesem Feld festgelegten Wert innerhalb der signierten Integritätsantwort zurück. Folgen Sie der Anleitung zum Generieren von Nonces, um Ihre Anwendung vor Angriffen zu schützen.

Klassische Anfragen mit exponentiellem Backoff wiederholen

Umgebungsbedingungen, z. B. 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 für ein ansonsten vertrauenswürdiges Gerät keine Labels generiert werden. Schließen Sie eine Wiederholungsoption mit exponentiellem Backoff ein, um diese Szenarien zu minimieren.

Übersicht

Sequenzdiagramm, das das allgemeine Design der Play Integrity API zeigt

Wenn der Nutzer eine wichtige Aktion in Ihrer App 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 diesen an die clientseitige Logik. In den verbleibenden Schritten wird diese Logik als Ihre „App“ bezeichnet.
  2. Ihre App erstellt den nonce aus dem eindeutigen Wert und dem Inhalt Ihrer Aktion mit hohem Wert. 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. Ihre App gibt das signierte und verschlüsselte Ergebnis an das Backend Ihrer App weiter.
  5. Das Backend Ihrer 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 deiner App zurück.
  6. Das Back-End Ihrer Anwendung entscheidet anhand der in der Token-Nutzlast enthaltenen Signale, wie fortzufahren.
  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 abzuwehren, z. B. Man-in-the-Middle-Angriffe (PITM) und Replay-Angriffe. Die Play Integrity API gibt den Wert zurück, den Sie in diesem Feld innerhalb der signierten Integritätsantwort festgelegt haben.

Der im Feld nonce festgelegte Wert muss richtig formatiert sein:

  • String
  • URL-sicher
  • Als Base64 codiert und nicht verpackt
  • Mindestens 16 Zeichen
  • Maximal 500 Zeichen

Im Folgenden findest du einige gängige Möglichkeiten, das Feld nonce in der Play Integrity API zu verwenden. Für den stärksten Schutz von nonce können Sie die folgenden Methoden kombinieren.

Anfrage-Hash zum Schutz vor Manipulation hinzufügen

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ätsergebnis anfordern, gilt Folgendes:

  1. Berechnen Sie einen Digest aller wichtigen Anfrageparameter (z.B. SHA256 einer stabilen Anfrageserialisierung) aus der laufenden Nutzeraktion oder Serveranfrage.
  2. Mit setNonce kannst du das Feld nonce auf den Wert des berechneten Digests festlegen.

Wenn du ein Integritätsergebnis erhältst, gilt Folgendes:

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

Eindeutige Werte zum Schutz vor Replay-Angriffen verwenden

Wenn Sie verhindern möchten, dass böswillige Nutzer frühere Antworten der Play Integrity API wiederverwenden, können Sie jede Nachricht mit dem Feld nonce eindeutig identifizieren.

Wenn Sie ein Integritätsergebnis anfordern, gilt Folgendes:

  1. Sie erhalten einen global eindeutigen Wert auf eine Weise, die böswillige Nutzer nicht vorhersagen können. Eine serverseitige kryptografisch sichere Zufallszahl kann beispielsweise ein solcher Wert oder eine bereits vorhandene ID wie eine Sitzungs- oder Transaktions-ID sein. Eine einfachere und weniger sichere Variante ist das Generieren einer Zufallszahl auf dem Gerät. Wir empfehlen, Werte mit einer Größe von mindestens 128 Bit zu erstellen.
  2. Rufen Sie setNonce() auf, um das Feld nonce auf den eindeutigen Wert aus Schritt 1 zu setzen.

Wenn du ein Integritätsergebnis erhältst, gilt Folgendes:

  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 die generierten Werte für einen geeigneten Zeitraum aufbewahren. Wenn der empfangene eindeutige Wert bereits verwendet wurde oder nicht im Eintrag enthalten ist, lehnen Sie die Anfrage ab.
  3. Wenn der eindeutige Wert auf dem Gerät generiert wurde, prüfen Sie andernfalls, ob der empfangene Wert zum ersten Mal verwendet wird (Ihr Server muss für einen geeigneten Zeitraum einen Datensatz der bereits gesehenen Werte aufbewahren). Wenn der empfangene eindeutige Wert bereits verwendet wurde, lehnen Sie die Anfrage ab.

Beide Schutzmaßnahmen gegen Manipulations- und Replay-Angriffe kombinieren (empfohlen)

Das Feld nonce kann verwendet werden, um gleichzeitig vor Manipulations- und Replay-Angriffen zu schützen. 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 Hash ist. Eine Implementierung, die beide Ansätze kombiniert, sieht so aus:

Wenn Sie ein Integritätsergebnis anfordern, gilt Folgendes:

  1. Der Nutzer initiiert die wertvolle Aktion.
  2. Rufen Sie einen eindeutigen Wert für diese Aktion ab, wie im Abschnitt Eindeutige Werte zum Schutz vor Replay-Angriffen schützen beschrieben.
  3. Bereiten Sie eine Nachricht vor, die Sie schützen möchten. Fügen Sie den eindeutigen Wert aus Schritt 2 in die Nachricht ein.
  4. Ihre Anwendung berechnet einen Digest der Nachricht, die geschützt werden soll, wie im Abschnitt Anfrage-Hash zum Schutz vor Manipulation hinzufügen beschrieben. Da die Nachricht den eindeutigen Wert enthält, ist dieser Teil des Hashs.
  5. Verwenden Sie setNonce(), um das Feld nonce auf den berechneten Digest aus dem vorherigen Schritt festzulegen.

Wenn du ein Integritätsergebnis erhältst, gilt Folgendes:

  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 Manipulation einschließen beschrieben, berechnen Sie den Digest auf der Serverseite neu und prüfen Sie, ob er mit dem Digest des Integritätstokens übereinstimmt.
  4. Prüfen Sie die Gültigkeit des eindeutigen Werts, wie im Abschnitt Eindeutige Werte zum Schutz vor Wiederholungsangriffen einbeziehen beschrieben.

Das folgende Sequenzdiagramm veranschaulicht diese Schritte mit einem serverseitigen nonce:

Sequenzdiagramm, das zeigt, wie man sich vor Manipulations- und Replay-Angriffen schützt

Integritätsergebnis anfordern

Nach dem Generieren eines nonce 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 das nonce über die Methode setNonce() im zugehörigen Builder an. Apps, die ausschließlich außerhalb von Google Play vertrieben werden, und SDKs, müssen 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 die Cloud-Projektnummer nicht in der Anfrage angeben.
  3. Rufe über den Manager requestIntegrityToken() auf und gib die 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();
}

Ich bin Muttersprachler

/// 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. Die nonce, die Sie in Ihrer Anfrage angeben, wird Teil des Antworttokens.

Token format

Das Token ist ein verschachteltes JSON-Webtoken (JWT), das der JSON-Webverschlüsselung (JWE) der JSON-Websignatur (JWS) entspricht. Die JWE- und JWS-Komponenten werden mithilfe einer kompakten 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 den Google-Servern entschlüsseln und bestätigen (empfohlen)

Mit der Play Integrity API kannst du das Integritätsergebnis auf den Google-Servern entschlüsseln und prüfen, was die Sicherheit deiner App erhöht. Führen Sie dazu die folgenden Schritte aus:

  1. Erstellen Sie ein Dienstkonto in dem Google Cloud-Projekt, das mit Ihrer Anwendung verknüpft ist.
  2. Rufen Sie auf dem Server Ihrer Anwendung das Zugriffstoken mit dem Bereich playintegrity 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 bestätigen

Wenn Sie Ihre Schlüssel zur Verschlüsselung von Antworten verwalten und herunterladen, können Sie das zurückgegebene Token innerhalb 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 in der Play Console in sprachspezifische Schlüssel (in unserem Fall die Programmiersprache Java) im Back-End der App decodiert werden. Beachten Sie, dass die Schlüssel mit Standard-Flags base64-codiert sind.

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.