Use the nonce field of the Play Integrity API to enhance the security of your app

Use the nonce field of the Play Integrity API to enhance the security of your app


Contributor Oscar Rodriguez, Developer Relations Engineer

Illustration of a mobile device displaying a checkmark, flowchart image, security shield with Android logo

With the recent release of the Play Integrity API, more developers are taking steps to protect their games and apps from potentially dangerous and malicious interactions.

The Play Integrity API has useful signals for app integrity, device integrity, and licensing information, as well as a simple yet very useful feature called a “nonce” that, when used correctly, already exists. You can further enhance your protection. The Play Integrity API not only mitigates, but also provides, certain types of attacks, such as Person-in-the-Middle (PITM) tampering and replay attacks.

In this blog post, we’ll take a closer look at what a nonce is, how it works, and how it can be used to further protect your app.

What is a nonce?

In encryption and security engineering, nonce (No. 11) Is a number that is used only once for secure communication. There are many applications in Nance, such as authentication, encryption, and hashing.

In the Play Integrity API, a nonce is an opaque base-64 encoded binary blob that is set before calling the API integrity check and is returned as-is in the API’s signed response. Depending on how the nonce is created and validated, the nonce can be leveraged to further enhance the existing protection provided by the Play Integrity API, or to launch certain types of attacks such as Person-in-the-Middle (PITM) tampering. It can be mitigated. Attacks and replay attacks.

The Play Integrity API does not perform any actual nonce data processing, except that it returns the nonce as is in the signed response. You can set any value as long as it is a valid base-64 value. However, in order to digitally sign the response, the nonce is sent to Google’s server, so you should not set the nonce on personally identifiable information (PII) such as the user’s name, phone number, email address, etc. It’s very important. ..

Nonce settings

After configuring your app to use the Play Integrity API, configure the nonce. setNonce() The method, or its appropriate variant. Available in Kotlin, Java, Unity, and native versions of the API.

Kotlin:

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) // Set the nonce
             .build())

Java:

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) // Set the nonce
            .build());

unit:

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

native:

/// Create an IntegrityTokenRequest object.
const char* nonce = ...
IntegrityTokenRequest* request;
IntegrityTokenRequest_create(&request);
IntegrityTokenRequest_setNonce(request, nonce); // Set the nonce
IntegrityTokenResponse* response;
IntegrityErrorCode error_code =
        IntegrityManager_requestIntegrityToken(request, &response);

Confirmation of nonce

The Play Integrity API response is returned in the form of JSON Web Token (JWT), whose payload is plain text JSON text, in the following format:

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
}

The nonce is inside requestDetails A structure formatted as follows:

requestDetails: {
  requestPackageName: "...",
  nonce: "...",
  timestampMillis: ...
}

The value of the nonce The field must exactly match the field you previously passed to the API. In addition, the nonce is in a cryptographically signed response of the Play Integrity API, so its value cannot be changed after the response is received. By leveraging these properties, you can use nonces to further protect your app.

Protecting high-value operations

Consider a scenario where a malicious user is operating an online game that reports a player’s score to a game server. In this case, the device is not at risk, but the user can view and modify the network data flow between the game and the server using a proxy server or VPN, allowing malicious users to report higher scores. .. The actual score will be much lower.

In this case, calling the Play Integrity API is not enough to protect your app. Since the device is not at risk and the app is legitimate, it passes all the checks performed by the Play Integrity API.

However, you can take advantage of the Play Integrity API nonce to protect this particular high-value operation that reports your game score by encoding the value of the operation within the nonce. The implementation is as follows:

  1. The user initiates a high-value action.
  2. The app prepares the message you want to protect, for example in JSON format.
  3. The app calculates the encryption hash of the message you want to protect. For example, use the SHA-256 or SHA-3-256 hash algorithm.
  4. The app calls the PlayIntegrity API setNonce() Set the nonce field to the encryption hash calculated in the previous step.
  5. The app sends both the message you want to protect and the signed result of the Play Integrity API to the server.
  6. The app server verifies that the encrypted hash of the received message matches the value in the nonce field of the signed result and rejects the mismatched result.

The following sequence diagram illustrates these steps.

Implementation diagram for encoding the value of the operation in the nonce. The procedure outlined in the body of the blog.

As long as the original message to be protected is sent with the signed result and both the server and the client use exactly the same mechanism for calculating the nonce, this is a strong guarantee that the message has not been tampered with.

In this scenario, the security model works on the assumption that the attack is occurring on the network rather than on the device or app, so it’s especially important to check the device-app integrity signal provided by the Play Integrity API. good.

Prevention of replay attacks

Consider another scenario where a malicious user is trying to interact with a server-client app protected by the PlayIntegrity API, but the server is trying to interact with the compromised device so that it doesn’t detect it.

To do this, the attacker first uses the app on a legitimate device to collect the signed response from the Play Integrity API. The attacker then uses the app on the compromised device, intercepts Play Integrity API calls, and instead of performing an integrity check, simply returns a previously recorded signed response.

Since the signed response hasn’t changed at all, the digital signature looks fine and can be misunderstood that the app server is communicating with a legitimate device.This is called Replay attack..

The first line of defense against such attacks is timestampMillis A field for the signed response. This field contains a time stamp when the response was created to help detect suspicious old responses, even if the digital signature is verified to be genuine.

However, you can also take advantage of the Play Integrity API nonce to assign a unique value to each response and ensure that the response matches a previously set unique value. The implementation is as follows:

  1. The server creates globally unique values ​​in a way that malicious users cannot predict. For example, a cryptographically secure random number of 128 bits or more.
  2. The app calls the PlayIntegrity API and sets the nonce field to a unique value received by the app server.
  3. The app sends the signed result of the PlayIntegrity API to the server.
  4. The server verifies that the nonce field in the signed result matches a previously generated unique value and rejects the unmatched result.

The following sequence diagram illustrates these steps.

A layout diagram that assigns a unique value to each response and ensures that the response matches a previously set unique value. The procedure outlined in the body of the blog.

In this implementation, each time the server requests the app to call the Play Integrity API, it calls it with a different globally unique value. Unless this value is predicted by the attacker, the previous response cannot be reused. , Because the nonce does not match the expected value.

Combine both protections

The above two mechanisms work in very different ways, but if your app needs both protections at the same time, for example, by adding the result of both protections to a larger one, a single PlayIntegrity API call. You can combine them with. base-64 nonce. The combined implementation of both approaches is:

  1. The user initiates a high-value action.
  2. The app requests a unique value from the server to identify the request
  3. App servers generate globally unique values ​​in a way that malicious users can’t predict. For example, you can use a cryptographically secure random number generator to create such a value. We recommend that you create a value that is 128 bits or larger.
  4. The app server sends a globally unique value to your app.
  5. The app prepares the message you want to protect, for example in JSON format.
  6. The app calculates the encryption hash of the message you want to protect. For example, use the SHA-256 or SHA-3-256 hash algorithm.
  7. The app creates a string by adding the unique value it receives from the app server and the hash of the message it protects.
  8. The app calls the PlayIntegrity API and calls setNonce () to set the nonce field to the string created in the previous step.
  9. The app sends both the message you want to protect and the signed result of the Play Integrity API to the server.
  10. The app server splits the value of the nonce field, verifies that the encrypted hash of the message and the previously generated unique value match the expected value, and rejects the mismatched result.

The following sequence diagram illustrates these steps.

Implementation diagram for combining both protections. The procedure outlined in the body of the blog.

These are examples of how you can use nonces to further protect your app from malicious users. If your app processes sensitive data or is vulnerable to unauthorized use, consider using the Play Integrity API to take steps to mitigate these threats.

For more information on using the Play Integrity API and how to get started, visit the g.co / play / integrity api documentation.