Làm việc với kết quả về tính toàn vẹn

API Tính toàn vẹn của Play sử dụng các kết quả về tính toàn vẹn để truyền đạt thông tin về tính hợp lệ của các thiết bị, ứng dụng và người dùng. Máy chủ ứng dụng có thể sử dụng tải trọng phát sinh trong một kết quả đã được giải mã và xác minh nhằm xác định cách tốt nhất để xử lý một hành động hoặc yêu cầu cụ thể trong ứng dụng của bạn.

Tạo một số chỉ dùng một lần

Khi dùng một API Tính toàn vẹn của Play để bảo vệ một hành động trong ứng dụng của mình, bạn có thể tận dụng trường nonce để giảm thiểu một số loại tấn công, chẳng hạn như tấn công xen giữa (PITM) và tấn công lặp lại (replay attack). API Tính toàn vẹn của Play sẽ trả về giá trị bạn đặt trong trường này, bên trong phản hồi về tính toàn vẹn đã ký.

Giá trị đã đặt trong trường nonce phải được định dạng chính xác:

  • String
  • URL-safe
  • Mã hoá dưới dạng Base64 và không đóng gói
  • Tối thiểu 16 ký tự
  • Tối đa 500 ký tự

Sau đây là một số cách phổ biến để sử dụng trường nonce trong API Tính toàn vẹn của Play. Để có được biện pháp bảo vệ mạnh nhất từ số dùng một lần này, bạn có thể kết hợp các phương thức dưới đây.

Bảo vệ các hành động có giá trị cao khỏi hành vi phá rối

Bạn cũng có thể sử dụng trường nonce của API Tính toàn vẹn của Play để bảo vệ nội dung của một hành động cụ thể có giá trị cao trước hành vi phá rối. Ví dụ: một trò chơi có thể cần báo cáo điểm số của người chơi và bạn muốn đảm bảo điểm số này chưa bị một máy chủ proxy xáo trộn. Việc triển khai thực hiện như sau:

  1. Người dùng bắt đầu hành động có giá trị cao.
  2. Ứng dụng sẽ chuẩn bị một thông báo mà ứng dụng muốn bảo vệ, ví dụ như ở định dạng JSON.
  3. Ứng dụng sẽ tính toán hàm băm mã hoá của thông báo mà ứng dụng muốn bảo vệ. Ví dụ, bằng thuật toán băm SHA-256 hoặc SHA-3-256.
  4. Ứng dụng của bạn gọi API Tính toàn vẹn của Play và gọi setNonce() để đặt trường nonce thành hàm băm mã hoá đã tính toán ở bước trước.
  5. Ứng dụng sẽ gửi cả thông báo mà ứng dụng muốn bảo vệ và kết quả đã ký của API Tính toàn vẹn của Play đến máy chủ.
  6. Máy chủ ứng dụng sẽ xác minh rằng hàm băm mã hoá của thông báo mà hệ thống nhận được khớp với giá trị của trường nonce trong kết quả đã ký và từ chối mọi kết quả không khớp.

Hình 1 chứa sơ đồ trình tự minh hoạ các bước sau:

Hình 1. Sơ đồ trình tự cho biết cách bảo vệ các hành động có giá trị cao trong ứng dụng của bạn khỏi hành vi phá rối.

Bảo vệ ứng dụng của bạn khỏi các cuộc tấn công phát lại

Để ngăn người dùng độc hại sử dụng lại những phản hồi trước đó từ API Tính toàn vẹn của Play, bạn có thể sử dụng trường nonce để xác định riêng biệt từng thông báo . Việc triển khai thực hiện như sau:

  1. Bạn cần một giá trị duy nhất trên toàn hệ thống mà người dùng độc hại không thể dự đoán. Ví dụ, giá trị này có thể là một con số ngẫu nhiên được bảo mật mã hoá, do phía máy chủ tạo ra. Bạn nên tạo các giá trị từ 128 bit trở lên.
  2. Ứng dụng của bạn gọi API Tính toàn vẹn của Play và gọi setNonce() để đặt trường nonce thành giá trị duy nhất mà máy chủ ứng dụng của bạn nhận được.
  3. Ứng dụng của bạn sẽ gửi kết quả đã ký của API tính toàn vẹn của Play đến máy chủ.
  4. Máy chủ sẽ xác minh rằng trường nonce trong kết quả đã ký khớp với giá trị duy nhất đã tạo trước đó và từ chối mọi kết quả không khớp.

Hình 2 chứa sơ đồ trình tự minh hoạ cho các bước sau:

Hình 2. Sơ đồ trình tự cho biết cách bảo vệ ứng dụng của bạn khỏi các cuộc tấn công lặp lại (replay attack).

Kết hợp cả hai biện pháp bảo vệ

Bạn có thể sử dụng trường nonce để bảo vệ cùng lúc trước cả các cuộc tấn công phát lại và phá rối. Để làm điều này, bạn có thể thêm máy chủ được tạo duy nhất trên toàn hệ thống vào hàm băm của thông báo giá trị cao và đặt giá trị này làm trường nonce trong API Tính toàn vẹn của Play. Cách triển khai kết hợp cả hai phương pháp thực hiện như sau:

  1. Người dùng bắt đầu hành động có giá trị cao.
  2. Ứng dụng của bạn yêu cầu máy chủ cung cấp một giá trị duy nhất để xác định yêu cầu
  3. Máy chủ ứng dụng của bạn tạo ra một giá trị duy nhất trên toàn hệ thống để người dùng độc hại không thể dự đoán. Ví dụ: bạn có thể sử dụng trình tạo số ngẫu nhiên được bảo mật bằng mật mã để tạo một giá trị như vậy. Bạn nên tạo các giá trị 128 bit trở lên.
  4. Máy chủ ứng dụng của bạn gửi giá trị duy nhất trên toàn hệ thống cho ứng dụng.
  5. Ứng dụng sẽ chuẩn bị một thông báo mà ứng dụng muốn bảo vệ, ví dụ như ở định dạng JSON.
  6. Ứng dụng sẽ tính toán hàm băm mã hoá của thông báo mà ứng dụng muốn bảo vệ. Ví dụ, bằng thuật toán băm SHA-256 hoặc SHA-3-256.
  7. Ứng dụng sẽ tạo một chuỗi bằng cách thêm giá trị duy nhất được gửi từ máy chủ ứng dụng và hàm băm của thông báo mà ứng dụng muốn bảo vệ.
  8. Ứng dụng của bạn gọi API Tính toàn vẹn của Play và gọi setNonce() để đặt trường nonce thành chuỗi đã tạo ở bước trước.
  9. Ứng dụng sẽ gửi cả thông báo mà ứng dụng muốn bảo vệ và kết quả đã ký của API Tính toàn vẹn của Play đến máy chủ.
  10. Máy chủ ứng dụng của bạn sẽ chia nhỏ giá trị của trường nonce và xác minh rằng các hàm băm mã hoá của thông báo cũng như giá trị duy nhất mà thông báo đã tạo trước đây khớp với giá trị dự kiến và từ chối mọi kết quả không khớp.

Hình 3 chứa sơ đồ trình tự minh hoạ cho các bước sau:

Hình 3. Sơ đồ trình tự cho biết cách bảo vệ ứng dụng của bạn trước các cuộc tấn công lặp lại cũng như bảo vệ các hành động có giá trị cao trong ứng dụng khỏi hành vi phá rối.

Yêu cầu kết quả về tính toàn vẹn

Sau khi tạo một số dùng một lần, bạn có thể yêu cầu Google Play đưa ra một kết luận về tính toàn vẹn. Để làm điều này, hãy hoàn tất các bước sau:

  1. Tạo một IntegrityManager như trong các ví dụ sau.
  2. Tạo IntegrityTokenRequest, cung cấp các số chỉ dùng một lần thông qua phương thức setNonce() trong trình tạo được liên kết. Các ứng dụng được phân phối độc quyền bên ngoài Google Play và các SDK cũng phải chỉ định số dự án trên Google Cloud thông qua phương thức setCloudProjectNumber(). Các ứng dụng trên Google Play được liên kết với một dự án Cloud trong Play Console mà không cần đặt số dự án Cloud trong yêu cầu.
  3. Sử dụng trình quản lý để gọi requestIntegrityToken(), cung cấp 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();
}

Mã gốc

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

Giải mã và xác minh kết quả về tính toàn vẹn

Khi bạn yêu cầu một kết quả về tính toàn vẹn, API Tính toàn vẹn của Play sẽ cung cấp một mã thông báo phản hồi đã ký. Giá trị chỉ dùng một lần mà bạn đưa vào yêu cầu sẽ trở thành một phần của mã thông báo phản hồi.

Định dạng mã thông báo

Mã thông báo là một Mã thông báo web JSON (JWT) lồng ghép, đó là Mã hoá web JSON (JWE) của Chữ ký web JSON (JWS) Các thành phần JWE và JWS được biểu thị bằng quá trình chuyển đổi tuần thự nhỏ gọn.

Các thuật toán mã hoá / ký tên được hỗ trợ tốt trong nhiều cách triển khai JWT khác nhau:

  • JWE sử dụng A256KW cho alg và A256GCM cho enc.
  • JWS sử dụng ES256.

Giải mã và xác minh trên các máy chủ của Google (được khuyến nghị)

API Tính toàn vẹn của Play giúp bạn giải mã và xác minh kết quả về tính toàn vẹn trên các máy chủ của Google, nhằm tăng cường bảo mật cho ứng dụng của bạn. Để làm điều đó, hãy hoàn thành các bước sau:

  1. Tạo một tài khoản dịch vụ trong dự án Google Cloud được liên kết với ứng dụng của bạn. Trong quá trình tạo tài khoản này, bạn cần cấp cho tài khoản dịch vụ của bạn vai trò Service Account User (Người dùng tài khoản dịch vụ) và Service Usage Consumer (Người tiêu dùng sử dụng dịch vụ).
  2. Trên máy chủ của ứng dụng, tìm nạp mã truy cập từ thông tin xác thực tài khoản dịch vụ của bạn bằng phạm vi playintegrity và thực hiện yêu cầu sau:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
      '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Đọc phản hồi JSON.

Giải mã và xác minh trên thiết bị

Nếu chọn quản lý và tải khoá mã hoá phản hồi xuống, bạn có thể giải mã và xác minh mã thông báo được trả về trong môi trường máy chủ bảo mật của riêng mình. Bạn có thể lấy mã thông báo được trả về bằng cách sử dụng phương thức IntegrityTokenResponse#token().

Ví dụ sau đây cho thấy cách giải mã khoá AES và khoá EC công khai được mã hoá bằng DER để xác minh chữ ký từ Play Console gửi tới các khoá cho ngôn ngữ cụ thể (trong trường hợp này là ngôn ngữ lập trình Java) trong phần phụ trợ của ứng dụng. Lưu ý rằng các khoá này được mã hoá dưới dạng base64 bằng cách sử dụng cờ mặc định.

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

Tiếp theo, dùng các khoá này để giải mã mã thông báo tính toàn vẹn (phần JWE) trước, sau đó xác minh và trích xuất phần JWS lồng ghép.

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

Trọng tải phát sinh là một mã thông báo dạng văn bản thuần tuý chứa các tín hiệu về tính toàn vẹn.

Định dạng phần tải trọng trả về

Gói dữ liệu này là tệp JSON văn bản thuần tuý chứa các tín hiệu về tính toàn vẹn cùng với thông tin do nhà phát triển cung cấp.

Tải trọng có cấu trúc tổng quát như sau:

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

Trước tiên, bạn phải kiểm tra để đảm bảo rằng các giá trị trong trường requestDetails khớp với các giá trị của yêu cầu ban đầu trước khi kiểm tra từng kết quả về tính toàn vẹn.

Phần sau đây sẽ mô tả chi tiết hơn về từng trường.

Trường thông tin chi tiết về yêu cầu

Trường requestDetails chứa thông tin đã cung cấp trong yêu cầu, bao gồm cả số chỉ dùng một lần.

requestDetails: {
  // Application package name this attestation was requested for.
  // Note that this field might be spoofed in the middle of the
  // request.
  requestPackageName: "com.package.name"
  // base64-encoded URL-safe no-wrap nonce provided by the developer.
  nonce: "aGVsbG8gd29scmQgdGhlcmU"
  // The timestamp in milliseconds when the request was made
  // (computed on the server).
  timestampMillis: "1617893780"
}

Các giá trị này phải khớp với các giá trị trong yêu cầu ban đầu. Do đó, hãy xác minh phần requestDetails trong tải trọng JSON bằng cách đảm bảo requestPackageNamenonce khớp với nội dung đã gửi trong yêu cầu ban đầu như được minh hoạ trong đoạn mã sau:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("requestDetails")
val requestPackageName = requestDetails.getString("requestPackageName")
val nonce = requestDetails.getString("nonce")
val timestampMillis = requestDetails.getLong("timestampMillis")
val currentTimestampMillis = ...

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request. See “Generate nonce”
        // section of the doc on how to store/compute the expected nonce.
    || !nonce.equals(expectedNonce)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("requestDetails");
String requestPackageName = requestDetails.getString("requestPackageName");
String nonce = requestDetails.getString("nonce");
long timestampMillis = requestDetails.getLong("timestampMillis");
long currentTimestampMillis = ...;

// Ensure the token is from your app.
if (!requestPackageName.equals(expectedPackageName)
        // Ensure the token is for this specific request. See “Generate nonce”
        // section of the doc on how to store/compute the expected nonce.
    || !nonce.equals(expectedNonce)
        // Ensure the freshness of the token.
    || currentTimestampMillis - timestampMillis > ALLOWED_WINDOW_MILLIS) {
        // The token is invalid! See below for further checks.
        ...
}

Trường tính toàn vẹn của ứng dụng

Trường appIntegrity chứa thông tin liên quan đến gói.

appIntegrity: {
  // PLAY_RECOGNIZED, UNRECOGNIZED_VERSION, or UNEVALUATED.
  appRecognitionVerdict: "PLAY_RECOGNIZED"
  // The package name of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  packageName: "com.package.name"
  // The sha256 digest of app certificates.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  certificateSha256Digest: ["6a6a1474b5cbbb2b1aa57e0bc3"]
  // The version of the app.
  // This field is populated iff appRecognitionVerdict != UNEVALUATED.
  versionCode: "42"
}

appRecognitionVerdict có thể có các giá trị sau:

PLAY_RECOGNIZED
Ứng dụng và chứng chỉ khớp với các phiên bản do Google Play phân phối.
UNRECOGNIZED_VERSION
Chứng chỉ hoặc tên gói không khớp với bản ghi của Google Play.
UNEVALUATED
Tính toàn vẹn của ứng dụng chưa được đánh giá. Thiếu một yêu cầu cần thiết, chẳng hạn như thiết bị không đủ tin cậy.

Để đảm bảo rằng mã thông báo được tạo bởi một ứng dụng mà bạn tạo ra, hãy xác minh rằng tính toàn vẹn của ứng dụng đúng với dự kiến như trong đoạn mã sau:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("appIntegrity")
val appRecognitionVerdict = requestDetails.getString("appRecognitionVerdict")

if (appRecognitionVerdict == "PLAY_RECOGNIZED") {
    // Looks good!
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("appIntegrity");
String appRecognitionVerdict =
    requestDetails.getString("appRecognitionVerdict");

if (appRecognitionVerdict.equals("PLAY_RECOGNIZED")) {
    // Looks good!
}

Bạn cũng có thể kiểm tra tên gói, phiên bản và chứng chỉ của ứng dụng theo cách thủ công.

Trường tính toàn vẹn của thiết bị

Trường deviceIntegrity có thể chứa một giá trị deviceRecognitionVerdict duy nhất. Giá trị này có một hoặc nhiều nhãn thể hiện mức độ hiệu quả của thiết bị trong việc thực thi tính toàn vẹn của ứng dụng. Nếu thiết bị không đáp ứng tiêu chí của nhãn nào, thì trường deviceIntegrity sẽ trống.

deviceIntegrity: {
  // "MEETS_DEVICE_INTEGRITY" is one of several possible values.
  deviceRecognitionVerdict: ["MEETS_DEVICE_INTEGRITY"]
}

Theo mặc định, deviceRecognitionVerdict có thể có một trong các nhãn sau:

MEETS_DEVICE_INTEGRITY
Ứng dụng này đang chạy trên một thiết bị Android được hỗ trợ trên Dịch vụ Google Play. Thiết bị này vượt qua quy trình kiểm tra tính toàn vẹn của hệ thống và đáp ứng các yêu cầu về khả năng tương thích với Android.
Không có nhãn (giá trị trống)
Ứng dụng đang chạy trên một thiết bị có dấu hiệu bị tấn công (chẳng hạn như hook API) hoặc bị xâm phạm hệ thống (chẳng hạn như đã bị can thiệp vào hệ thống) hoặc ứng dụng không chạy trên thiết bị thực tế (chẳng hạn như trình mô phỏng không vượt qua được quy trình kiểm tra tính toàn vẹn của Google Play).

Để đảm bảo rằng mã thông báo đến từ một thiết bị đáng tin cậy, hãy xác minh deviceRecognitionVerdict theo dự kiến như trong đoạn mã sau:

Kotlin

val deviceIntegrity =
                JSONObject(payload).getJSONObject("deviceIntegrity")
val deviceRecognitionVerdict =
                if (deviceIntegrity.has("deviceRecognitionVerdict")) {
                    deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString()
                } else {
                    ""
                }

if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) {
    // Looks good!
}

Java

JSONObject deviceIntegrity =
                new JSONObject(payload).getJSONObject("deviceIntegrity");
String deviceRecognitionVerdict =
                deviceIntegrity.has("deviceRecognitionVerdict")
                ? deviceIntegrity.getJSONArray("deviceRecognitionVerdict").toString()
                : "";

if (deviceRecognitionVerdict.contains("MEETS_DEVICE_INTEGRITY")) {
    // Looks good!
}

Nếu bạn gặp sự cố về tính toàn vẹn của thiết bị đang kiểm tra, hãy đảm bảo bạn đã cài đặt ROM gốc (ví dụ như đặt lại thiết bị) và khoá trình tải khởi động. Bạn cũng có thể tạo các bài kiểm tra tính toàn vẹn của Play trong Play Console.

Nếu bạn chọn nhận các nhãn bổ sung trong kết quả về tính toàn vẹn, deviceRecognitionVerdict có thể có các nhãn bổ sung sau:

MEETS_BASIC_INTEGRITY
Ứng dụng này đang chạy trên một thiết bị đã vượt qua các bài kiểm tra cơ bản về tính toàn vẹn của hệ thống. Có thể thiết bị này không đáp ứng yêu cầu về khả năng tương thích với Android và không được phê duyệt để sử dụng dịch vụ Google Play. Ví dụ, có thể thiết bị này đang chạy một phiên bản Android không xác định, dùng trình tải khởi động đã được mở khoá hoặc chưa được nhà sản xuất chứng nhận.
MEETS_STRONG_INTEGRITY
Ứng dụng này đang chạy trên một thiết bị Android được hỗ trợ các dịch vụ của Google Play, đồng thời đảm bảo chắc chắn tính toàn vẹn của hệ thống, chẳng hạn như bằng chứng dựa trên phần cứng về tính toàn vẹn khởi động. Thiết bị vượt qua quy trình kiểm tra tính toàn vẹn của hệ thống và đáp ứng các yêu cầu về khả năng tương thích với Android.

Ngoài ra, nếu chúng tôi phát hành ứng dụng cho trình mô phỏng được phê duyệt, thì deviceRecognitionVerdict cũng có thể gắn nhãn sau:

MEETS_VIRTUAL_INTEGRITY
Ứng dụng này đang chạy trên một giả lập Android được hỗ trợ trên Dịch vụ Google Play. Trình mô phỏng vượt qua các bài kiểm tra tính toàn vẹn của hệ thống và đáp ứng các yêu cầu chính về khả năng tương thích với Android.

Trường thông tin chi tiết tài khoản

Trường accountDetails chứa một giá trị appLicensingVerdict duy nhất, đại diện cho trạng thái cấp phép/quyền của ứng dụng.

accountDetails: {
  // This field can be LICENSED, UNLICENSED, or UNEVALUATED.
  appLicensingVerdict: "LICENSED"
}

appLicensingVerdict có thể có các giá trị sau:

LICENSED
Người dùng có quyền sử dụng ứng dụng. Nói cách khác, người dùng đã cài đặt hoặc mua ứng dụng của bạn trên Google Play.
UNLICENSED
Người dùng không có quyền sử dụng ứng dụng. Điều này xảy ra khi người dùng không tải ứng dụng của bạn qua cửa hàng hoặc có được ứng dụng qua nơi khác ngoài Google Play.
UNEVALUATED

Thông tin cấp phép chưa được đánh giá vì thiếu một yêu cầu cần thiết.

Điều này có thể xảy ra vì một vài lý do như:

  • Thiết bị không đủ tin cậy.
  • Google Play không xác định được phiên bản ứng dụng trên thiết bị đó.
  • Người dùng chưa đăng nhập vào Google Play.

Để kiểm tra xem người dùng có quyền sử dụng ứng dụng của bạn hay không, hãy xác minh rằng appLicensingVerdict là đúng với dự kiến như trong đoạn mã sau:

Kotlin

val requestDetails = JSONObject(payload).getJSONObject("accountDetails")
val appLicensingVerdict = requestDetails.getString("appLicensingVerdict")

if (appLicensingVerdict == "LICENSED") {
    // Looks good!
}

Java

JSONObject requestDetails =
    new JSONObject(payload).getJSONObject("accountDetails");
String appLicensingVerdict = requestDetails.getString("appLicensingVerdict");

if (appLicensingVerdict.equals("LICENSED")) {
    // Looks good!
}