Standard-API-Anfrage stellen

Auf dieser Seite wird beschrieben, wie Sie Standard-API-Anfragen für Integritätsbewertungen stellen, die auf Android 5.0 (API-Level 21) oder höher unterstützt werden. Sie können eine Standard-API-Anfrage für ein Integritätsurteil stellen, wenn Ihre App einen Serveraufruf ausführt, um zu prüfen, ob die Interaktion echt ist.

Übersicht

Sequenzdiagramm, das das allgemeine Design der Play Integrity API zeigt

Eine Standardanfrage besteht aus zwei Teilen:

  • Integritätstokenanbieter vorbereiten (einmalig): Sie müssen die Integrity API aufrufen, um den Integritätstokenanbieter gut vorzubereiten, bevor Sie das Integritätsurteil erhalten müssen. Sie können dies beispielsweise beim Starten Ihrer App oder im Hintergrund tun, bevor das Integritätsurteil benötigt wird.
  • Integritätstoken auf Anfrage anfordern: Jedes Mal, wenn Ihre App eine Serveranfrage sendet, deren Echtheit Sie prüfen möchten, fordern Sie ein Integritätstoken an und senden es zur Entschlüsselung und Überprüfung an den Backend-Server Ihrer App. Ihr Backend-Server kann dann entscheiden, wie er vorgehen soll.

Integritätstokenanbieter vorbereiten (einmalig):

  1. Ihre App ruft den Anbieter des Integritätstokens mit Ihrer Google Cloud-Projektnummer auf.
  2. Ihre App speichert den Anbieter des Integritätstokens im Arbeitsspeicher für weitere Aufrufe der Attestationsüberprüfung.

Integritätstoken anfordern (auf Anfrage):

  1. Für die zu schützende Nutzeraktion berechnet Ihre App den Hash (mit einem geeigneten Hash-Algorithmus wie SHA256) der angeforderten Daten.
  2. Ihre App fordert ein Integritätstoken an und gibt den Hash der Anfrage weiter.
  3. Ihre App erhält das signierte und verschlüsselte Integritätstoken von der Play Integrity API.
  4. Ihre App übergibt das Integritätstoken an das Backend Ihrer App.
  5. Das Backend Ihrer App sendet das Token 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.
  6. Das Back-End Ihrer App entscheidet basierend auf den Signalen in der Tokennutzlast, wie es fortfahren soll.
  7. Das Backend Ihrer App sendet die Entscheidung an Ihre App.

Integritätstokenanbieter vorbereiten (einmalig)

Bevor Sie eine Standardanfrage für ein Integritätsurteil von Google Play stellen, müssen Sie den Anbieter von Integrität-Tokens vorbereiten (oder „aufwärmen“). So kann Google Play teilweise Attestierungsinformationen intelligent auf dem Gerät im Cache speichern, um die Latenz auf dem kritischen Pfad zu verringern, wenn Sie ein Integritätsurteil anfordern. Wenn Sie den Tokenanbieter noch einmal vorbereiten, können Sie weniger ressourcenintensive Integritätsprüfungen wiederholen. Dadurch ist das nächste von Ihnen angeforderte Integritätsurteil aktueller.

Sie können den Anbieter von Integritätstokens so vorbereiten:

  • Beim Starten der App (d.h. beim Kaltstart). Die Vorbereitung des Tokenanbieters erfolgt asynchron und hat daher keinen Einfluss auf die Startzeit. Diese Option eignet sich gut, wenn Sie kurz nach der Veröffentlichung der App eine Anfrage für ein Integritätsurteil stellen möchten, z. B. wenn sich ein Nutzer anmeldet oder ein Spieler einem Spiel beitritt.
  • Wenn Ihre App geöffnet wird (d.h. beim Warmstart). Jede App-Instanz kann das Integritätstoken jedoch nur bis zu fünfmal pro Minute vorbereiten.
  • Sie können das Token jederzeit im Hintergrund vorbereiten, wenn Sie es vor einer Anfrage für ein Integritätsurteil vorbereiten möchten.

So bereiten Sie den Anbieter von Integritätstokens vor:

  1. Erstellen Sie ein StandardIntegrityManager, wie in den folgenden Beispielen gezeigt.
  2. Erstelle eine PrepareIntegrityTokenRequest und gib die Google Cloud-Projektnummer über die Methode setCloudProjectNumber() an.
  3. Rufe prepareIntegrityToken() über den Manager auf und gib dabei PrepareIntegrityTokenRequest an.

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();
}

Nativ

/// 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);

Anfragen vor Manipulation schützen (empfohlen)

Wenn Sie eine Nutzeraktion in Ihrer App mit der Play Integrity API prüfen, können Sie das Feld requestHash verwenden, um Manipulationsangriffe zu verhindern. Beispiel: In einem Spiel soll der Punktestand des Spielers an den Backend-Server des Spiels gesendet werden. Ihr Server soll dafür sorgen, dass dieser Wert nicht von einem Proxyserver manipuliert wurde. Die Play Integrity API gibt den Wert zurück, den Sie im Feld requestHash in der signierten Integritätantwort festgelegt haben. Ohne requestHash ist das Integritätstoken nur an das Gerät und nicht an die spezifische Anfrage gebunden. Dies eröffnet die Möglichkeit eines Angriffs. In der folgenden Anleitung wird beschrieben, wie Sie das Feld requestHash effektiv verwenden:

Wenn Sie ein Integritätsurteil beantragen, gilt Folgendes:

  • Berechnen Sie einen Digest aller relevanten Anfrageparameter (z.B. SHA256 einer stabilen Anfrageserialisierung) aus der Nutzeraktion oder Serveranfrage, die gerade ausgeführt wird. Der im Feld requestHash festgelegte Wert darf maximal 500 Byte lang sein. Fügen Sie in der requestHash alle App-Anfragedaten ein, die für die Aktion, die Sie prüfen oder schützen möchten, entscheidend oder relevant sind. Das Feld requestHash wird wörtlich in das Integritätstoken eingefügt. Lange Werte können die Anfragegröße erhöhen.
  • Geben Sie den Hashwert als requestHash-Feld in der Play Integrity API an und rufen Sie das Integritätstoken ab.

Wenn Sie ein Integritätsurteil erhalten:

  • Dekodiere das Integritättoken und extrahiere das Feld requestHash.
  • 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.

Integritätsergebnis anfordern (auf Anfrage)

Nachdem Sie den Anbieter von Integritätstokens vorbereitet haben, können Sie Integritätsergebnisse von Google Play anfordern. Gehen Sie dazu so vor:

  1. Holen Sie sich eine StandardIntegrityTokenProvider, wie oben gezeigt.
  2. Erstellen Sie eine StandardIntegrityTokenRequest und geben Sie den Anfrage-Hash der Nutzeraktion an, die Sie mit der setRequestHash-Methode schützen möchten.
  3. Rufe request() mit dem Anbieter des Integritätstokens auf und gib dabei StandardIntegrityTokenRequest an.

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();
}

Nativ

/// 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();

Integritätsurteil entschlüsseln und prüfen

Nachdem Sie ein Integritätsurteil angefordert haben, stellt die Play Integrity API ein verschlüsseltes Antworttoken bereit. Um die Ergebnisse zur Geräteintegrität zu erhalten, müssen Sie das Integritätstoken auf den Google-Servern entschlüsseln. Gehen Sie dazu so vor:

  1. Erstellen Sie ein Dienstkonto im Google Cloud-Projekt, das mit Ihrer App verknüpft ist.
  2. Rufen Sie auf dem Server Ihrer App das Zugriffstoken mit 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" }'
  3. Lies die JSON-Antwort.

Die resultierende Nutzlast ist ein Klartext-Token, das Integritätsbewertungen enthält.

Automatischer Schutz vor Replay-Angriffen

Um Replay-Angriffe zu vermeiden, sorgt Google Play automatisch dafür, dass jedes Integritätstoken nicht mehrmals wiederverwendet werden kann. Der wiederholte Versuch, dasselbe Token zu entschlüsseln, führt dazu, dass die Ergebnisse gelöscht werden. Bei Tokens mit Schutz vor Manipulation werden die decodierten Entscheidungen so zurückgegeben:

  • Das Ergebnis der Geräteerkennung ist leer.
  • Das Ergebnis der App-Erkennung und das Ergebnis der App-Lizenzierung werden auf UNEVALUATED festgelegt.
  • Alle optionalen Entscheidungen, die über die Play Console aktiviert sind, werden auf UNEVALUATED gesetzt (oder auf eine leere Entscheidung, wenn es sich um eine Entscheidung mit mehreren Werten handelt).