Eseguire una richiesta API classica

Se prevedi di creare solo API standard , che sono adatte alla maggior parte delle sviluppatori, puoi passare direttamente all'integrità esiti. In questa pagina viene descritto come rendere Richieste API per esiti relativi all'integrità, che sono supportate su Android 4.4 (API livello 19) o superiore.

Considerazioni

Confronta le richieste standard e classiche

Puoi effettuare richieste standard, richieste classiche o una combinazione delle due a seconda delle esigenze della tua app in materia di sicurezza e comportamenti illeciti. Le richieste standard sono adatto a tutti i giochi e le app e può essere utilizzato per verificare che qualsiasi azione o la chiamata al server è autentica e delega una certa protezione contro la rigiocabilità ed esfiltrazione su Google Play. Le richieste classiche sono più costose è tua responsabilità implementarle correttamente per proteggere esfiltrazione e alcuni tipi di attacchi. Le richieste classiche dovrebbero essere inferiori spesso rispetto alle richieste standard, ad esempio una tantum se un'azione molto redditizia o sensibile è autentica.

La seguente tabella evidenzia le principali differenze tra i due tipi di richieste:

Richiesta API standard Richiesta API classica
Prerequisiti
Versione minima dell'SDK Android richiesta Android 5.0 (livello API 21) o versioni successive Android 4.4 (livello API 19) o versioni successive
Requisiti di Google Play Google Play Store e Google Play Services Google Play Store e Google Play Services
Dettagli dell'integrazione
Riscaldamento dell'API richiesto ✔️ (pochi secondi)
Latenza tipica delle richieste Poche centinaia di millisecondi Pochi secondi
Frequenza potenziale delle richieste Frequente (controllo su richiesta per qualsiasi azione o richiesta) Raro (controllo una tantum delle azioni di valore più elevato o della maggior parte delle richieste sensibili)
Timeout La maggior parte delle fasi di riscaldamento dura meno di 10 secondi ma prevede una chiamata al server, quindi è consigliato un timeout lungo (ad es. 1 minuto). Le richieste di verdetto avvengono lato client La maggior parte delle richieste è inferiore a 10 secondi ma comporta una chiamata al server, quindi si consiglia un timeout lungo (ad es. 1 minuto)
Token di esito dell'integrità
Contiene i dettagli del dispositivo, dell'app e dell'account ✔️ ✔️
Memorizzazione nella cache dei token Memorizzazione nella cache on-device protetta da Google Play Selezione sconsigliata
Decripta e verifica il token tramite il server di Google Play ✔️ ✔️
Latenza tipica delle richieste server-server di decriptazione Decine di millisecondi con disponibilità di tre nove Decine di millisecondi con disponibilità di tre nove
Decripta e verifica il token localmente in un ambiente server sicuro ✔️
Decripta e verifica il token sul lato client
Aggiornamento dell'esito relativo all'integrità Memorizzazione nella cache e aggiornamento automatici da parte di Google Play Tutti gli esiti sono ricalcolati per ogni richiesta
limiti
Richieste per app al giorno 10.000 per impostazione predefinita (è possibile richiedere un aumento) 10.000 per impostazione predefinita (è possibile richiedere un aumento)
Richieste per istanza di app al minuto Riscaldamento: 5 al minuto
Token di integrità: nessun limite pubblico*
Token di integrità: 5 al minuto
Protezione
Mitigare contro manomissioni e attacchi simili Utilizza il campo requestHash Utilizza il campo nonce con associazione di contenuti basata sui dati della richiesta
Mitigare contro le repliche e gli attacchi simili Mitigazione automatica di Google Play Utilizza il campo nonce con la logica lato server

* Tutte le richieste, incluse quelle senza limiti pubblici, siano soggetti a limiti difensivi non pubblici se sono soggetti a valori elevati.

Effettua richieste classiche raramente

La generazione di un token di integrità richiede tempo, dati e batteria. Inoltre, ogni app ha un numero massimo di richieste classiche che può effettuare al giorno. Pertanto, dovresti effettuare richieste classiche solo per verificare il valore più elevato o le azioni più sensibili sono autentici quando si desidera una garanzia aggiuntiva rispetto a una richiesta standard. Tu non devono effettuare richieste classiche per azioni ad alta frequenza o di scarso valore. Azioni sconsigliate effettuare richieste classiche ogni volta che l'app passa in primo piano o a intervalli minuti in background ed evitare di chiamare da un numero elevato di dispositivi contemporaneamente. Un'app che effettua troppe chiamate di richieste classiche potrebbe essere limitata a proteggere gli utenti da implementazioni errate.

Evita gli esiti della memorizzazione nella cache

La memorizzazione nella cache di un verdetto aumenta il rischio di attacchi come esfiltrazione e riproduzione, in cui un esito positivo viene riutilizzato da un ambiente non attendibile. Se fare una richiesta classica e memorizzarla nella cache per usarla in un secondo momento, è l'opzione consigliata invece di eseguire una richiesta standard on demand. Richieste standard comporta la memorizzazione nella cache del dispositivo, ma Google Play utilizza tecniche per ridurre il rischio di attacchi di ripetizione ed esfiltrazione.

Utilizzare il campo nonce per proteggere le richieste classiche

L'API Play Integrity offre un campo chiamato nonce, che può essere utilizzato per: proteggere ulteriormente la tua app da determinati attacchi, come la riproduzione e le manomissioni attacchi informatici. L'API Play Integrity restituisce il valore impostato in questo campo, all'interno la risposta relativa all'integrità firmata. Segui attentamente le indicazioni su come generare nonce per proteggere la tua app dagli attacchi.

Riprova le richieste classiche con backoff esponenziale

Condizioni ambientali, quali una connessione Internet instabile o un dispositivo sovraccarico, può causare il mancato controllo dell'integrità del dispositivo. Questo può determinare non viene generata alcuna etichetta per un dispositivo altrimenti attendibile. A per mitigare questi scenari, includi un'opzione di nuovo tentativo con backoff esponenziale.

Panoramica

Diagramma di sequenza che mostra il design generale di Play Integrity
API

Quando l'utente esegue nella tua app un'azione di alto valore che vuoi proteggere con un controllo di integrità, completa i seguenti passaggi:

  1. Il backend lato server dell'app genera e invia un valore univoco al componente dalla logica lato client. I passaggi rimanenti fanno riferimento a questa logica come "app".
  2. L'app crea nonce dal valore univoco e dai contenuti di un'azione di alto valore. Quindi chiama l'API Play Integrity, passando nonce.
  3. La tua app riceve un esito firmato e criptato da Play Integrity tramite Google Cloud CLI o tramite l'API Compute Engine.
  4. L'app trasmette l'esito firmato e criptato al relativo backend.
  5. Il backend dell'app invia l'esito a un server Google Play. Il team di Il server di Google Play decripta e verifica l'esito, restituendo i risultati al tuo il backend dell'app.
  6. Il backend dell'app decide come procedere, in base agli indicatori contenuti nelle il payload del token.
  7. Il backend dell'app invia i risultati della decisione all'app.

Genera un nonce

Se proteggi un'azione nella tua app con l'API Play Integrity, puoi sfruttare il campo nonce per mitigare determinati tipi di attacchi, come manomissione degli attacchi person in the middle (PITM) e replica degli attacchi. The Play L'API Integrity restituisce il valore impostato in questo campo all'interno del campo e la risposta all'integrità.

Il valore impostato nel campo nonce deve essere formattato correttamente:

  • String
  • Sicuro per URL
  • Codificato come Base64 e senza wrapping
  • Minimo 16 caratteri
  • Massimo 500 caratteri

Di seguito sono riportati alcuni modi comuni per utilizzare il campo nonce in Google Play API Integrity. Per ottenere la protezione più efficace da nonce, puoi combinare i metodi indicati di seguito.

Includi un hash della richiesta per proteggere da manomissioni

Puoi utilizzare il parametro nonce in una richiesta API classica in modo simile alle requestHash in una richiesta API standard per proteggere i contenuti di un di impedire manomissioni.

Quando richiedi un esito relativo all'integrità:

  1. Calcola un digest di tutti i parametri critici della richiesta (ad es. SHA256 di un modello serializzazione) dall'azione utente o dalla richiesta del server che in cui ciò che accade.
  2. Usa setNonce per impostare il campo nonce sul valore del digest calcolato.

Quando ricevi un esito relativo all'integrità:

  1. Decodifica e verifica il token di integrità e ottieni il digest dal campo nonce.
  2. Calcola un digest della richiesta come fai nell'app (ad es. SHA256 di una serializzazione di richiesta stabile).
  3. Confronta le sintesi lato app e lato server. Se non corrispondono, della richiesta non è attendibile.
di Gemini Advanced.

Includi valori univoci per la protezione dagli attacchi di ripetizione

Per impedire a utenti malintenzionati di riutilizzare risposte precedenti da parte di API Play Integrity, puoi usare il campo nonce per identificare in modo univoco ogni .

Quando richiedi un esito relativo all'integrità:

  1. Ottieni un valore globalmente univoco in modo da non essere in grado di prevedere gli utenti malintenzionati. Ad esempio, un numero casuale con crittografia sicuro generato nella lato server può essere un valore di questo tipo o un ID preesistente, come una sessione un ID transazione. Una variante più semplice e meno sicura è quella di generare numero sul dispositivo. Consigliamo di creare valori di almeno 128 bit.
  2. Richiama setNonce() per impostare il campo nonce sul valore univoco del passaggio 1.

Quando ricevi un esito relativo all'integrità:

  1. Decodifica e verifica il token di integrità e ottieni il valore univoco dal campo nonce.
  2. Se il valore del passaggio 1 è stato generato sul server, verifica che il valore ricevuto è uno dei valori generati e risulta utilizzata per la prima volta (il server dovrà conservare un record delle per una durata adatta). Se il valore univoco ricevuto è stato utilizzato appare già o non appare nel record, rifiuta la richiesta
  3. In caso contrario, se il valore univoco è stato generato sul dispositivo, verifica che il valore valore ricevuto viene utilizzato per la prima volta (il server deve conservare un di valori già visualizzati per una durata adatta). Se ha ricevuto è già stato usato. Rifiuta la richiesta.

Combina entrambe le protezioni contro gli attacchi di manomissione e ripetizione (opzione consigliata)

È possibile utilizzare il campo nonce per proteggere sia da manomissioni che di ripetere gli attacchi contemporaneamente. Per farlo, genera il valore univoco descritti sopra e includerli nella tua richiesta. Quindi calcola il hash della richiesta, accertandoti di includere il valore univoco nell'hash. Un che combina entrambi gli approcci:

Quando richiedi un esito relativo all'integrità:

  1. L'utente avvia l'azione di alto valore.
  2. Ottieni un valore univoco per questa azione, come descritto nella sezione Includi indirizzi i valori per proteggere dagli attacchi di ripetizione.
  3. Prepara un messaggio che vuoi proteggere. Includi il valore univoco del passaggio 2 nel messaggio.
  4. La tua app calcola una sintesi del messaggio che vuole proteggere, descritta nella sezione Includere un hash della richiesta per proteggere manomissioni. Poiché il messaggio contiene il parametro , il valore univoco fa parte dell'hash.
  5. Utilizza setNonce() per impostare il campo nonce sul digest calcolato dal passaggio precedente.

Quando ricevi un esito relativo all'integrità:

  1. Ottieni il valore unico dalla richiesta
  2. Decodifica e verifica il token di integrità e ottieni il digest dal campo nonce.
  3. Come descritto nella sezione Includi un hash della richiesta per evitare manomissioni ricalcola il digest sul lato server e controlla che corrisponda il digest ottenuto dal token di integrità.
  4. Come descritto in Includi valori univoci per proteggerli dalla riproduzione. , verifica la validità del valore univoco.

Il seguente diagramma di sequenza illustra questi passaggi con un server nonce:

Diagramma di sequenza che mostra come proteggerti sia da manomissioni che da ripetizione
attacchi

Richiedi un esito relativo all'integrità

Dopo aver generato un nonce, puoi richiedere a Google un esito relativo all'integrità Gioca. Per farlo, segui questi passaggi:

  1. Crea un'IntegrityManager, come mostrato negli esempi seguenti.
  2. Crea un IntegrityTokenRequest, fornendo il valore nonce tramite setNonce() nel builder associato. App distribuite in modo esclusivo al di fuori di Google Play e gli SDK devono specificare anche il numero di progetto con il metodo setCloudProjectNumber(). App su Google Google Play sono collegati a un progetto Cloud in Play Console e non devono imposta il numero di progetto Cloud nella richiesta.
  3. Utilizza l'amministratore per chiamare requestIntegrityToken(), fornendo il metodo IntegrityTokenRequest.

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

Nativo

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

Decripta e verifica l'esito relativo all'integrità

Quando richiedi un esito relativo all'integrità, l'API Play Integrity fornisce un esito relativo all'integrità token di risposta. Il nonce che includi nella richiesta diventa parte del token di risposta.

Formato token

Il token è un JWT (JSON Web Token) nidificato, che è JSON Web Encryption (JWE) della firma web JSON (JWS). I componenti JWE e JWS sono rappresentati utilizzando lo strumento serializzazione di Google.

Gli algoritmi di crittografia / firma sono ben supportati in vari JWT implementazioni:

  • JWE utilizza A256KW per alg e A256GCM per enc

  • JWS utilizza ES256.

Decripta e verifica sui server di Google (opzione consigliata)

L'API Play Integrity ti consente di decriptare e verificare l'esito relativo all'integrità Server di Google, che migliora la sicurezza della tua app. Per farlo, completa questi passaggi passaggi:

  1. Crea un account di servizio all'interno del progetto Google Cloud collegato alla tua app.
  2. Sul server dell'app, recupera il token di accesso dal tuo account di servizio utilizzando l'ambito playintegrity ed effettua la richiesta seguente:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Leggi la risposta JSON.

Decripta e verifica in locale

Se scegli di gestire e scaricare le chiavi di crittografia delle risposte, puoi decripta e verifica il token restituito all'interno del tuo ambiente server sicuro. Puoi ottenere il token restituito utilizzando IntegrityTokenResponse#token() .

L'esempio seguente mostra come decodificare la chiave AES e il file pubblico con codifica DER Chiave EC per la verifica della firma da Play Console alle lingue specifiche (nel nostro caso il linguaggio di programmazione Java) nel backend dell'app. Nota che le chiavi abbiano codifica base64 usando flag predefiniti.

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

Quindi, usa queste chiavi per decriptare prima il token di integrità (parte JWE), quindi verificare ed estrarre la parte JWS nidificata.

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

Il payload risultante è un token di testo normale che contiene integrità e decisioni.