提出標準 API 要求

本頁將說明如何提出標準 API 要求來取得完整性判定結果,您只要使用 Android 5.0 (API 級別 21) 以上版本,即可執行這項操作。每當應用程式發出伺服器呼叫,檢查互動情形是否確實時,您都可以提出標準 API 要求,索取完整性判定結果。

總覽

呈現 Play Integrity API 整體設計的循序圖

提出標準要求的程序包含兩個部分:

  • 備妥完整性權杖供應工具 (一次性):您需要先呼叫 Integrity API,備妥完整性權杖供應工具,之後才能在需要時取得完整性判定結果。舉例來說,您可以先在應用程式啟動當下或在背景執行這項操作,之後再索取完整性判定結果。
  • 索取完整性權杖 (隨選):每當應用程式發出伺服器要求,而您想檢查這項要求是否確實時,都應索取完整性權杖,然後將其傳送至應用程式的後端伺服器,用於解密及驗證。後端伺服器會接著決定要採取的因應措施。

備妥完整性權杖供應工具 (一次性):

  1. 應用程式使用 Google Cloud 專案編號,呼叫完整性權杖供應工具。
  2. 應用程式將完整性權杖供應工具儲存在記憶體中,方便進一步用於認證檢查呼叫。

索取完整性權杖 (隨選):

  1. 應用程式針對需要保護的使用者動作,使用任一適用的雜湊演算法 (例如 SHA256),計算所提要求的雜湊。
  2. 應用程式索取完整性權杖,傳遞要求雜湊。
  3. 應用程式從 Play Integrity API 接收已簽署及加密的完整性權杖。
  4. 應用程式將完整性權杖傳遞至應用程式後端。
  5. 應用程式後端將權杖傳送至 Google Play 伺服器。Google Play 伺服器解密並驗證判定結果後,將結果傳回應用程式後端。
  6. 應用程式後端根據權杖酬載中的信號,決定如何繼續操作。
  7. 應用程式後端將決定結果傳送至應用程式。

備妥完整性權杖供應工具 (一次性)

針對 Google Play 完整性判定結果提出標準要求之前,您必須先備妥完整性權杖供應工具,這也稱為「暖機」程序。如此一來,Google Play 就能靈敏地在裝置上快取部分認證資訊,進而在您索取完整性判定結果時,縮短關鍵路徑上的延遲時間。如要重複執行完整性檢查而不耗用大量資源,可以考慮再次準備權杖供應工具,這樣後續就能取得更即時的完整性判定結果。

您可以在下列情況準備完整性權杖供應工具:

  • 應用程式啟動時 (即冷啟動時)。權杖供應工具的準備作業並非同步操作,因此不會影響啟動時間。如果索取完整性判定結果的時間點預計在應用程式啟動後不久,例如使用者登入或玩家加入遊戲時,這個方法就非常實用。
  • 應用程式開啟時 (即暖啟動時)。但請注意,每個應用程式例項每分鐘最多只能準備完整性權杖 5 次。
  • 如果在背景執行,則可隨時在索取完整性判定結果前準備權杖。

如要準備完整性權杖供應工具,請按照下列指示操作:

  1. 依照以下範例建立 StandardIntegrityManager
  2. 建構 PrepareIntegrityTokenRequest,提供 Google Cloud 透過 setCloudProjectNumber() 方法建立專案編號
  3. 使用管理員呼叫 prepareIntegrityToken() 以提供 PrepareIntegrityTokenRequest

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

原生

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

防範有心人士竄改要求 (建議做法)

使用 Play Integrity API 檢查應用程式中的使用者動作時,您可以利用 requestHash 欄位應對竄改攻擊。舉例來說,遊戲可能要向遊戲後端伺服器回報玩家分數,而您的伺服器想確保 Proxy 伺服器未竄改分數,在此情況下,就適合使用上述欄位。Play Integrity API 會在已簽署的完整性回應內,傳回您在 requestHash 欄位中設定的值。如果沒有 requestHash,完整性權杖就只會繫結至裝置,而不會繫結至特定要求,因此可能遭受攻擊。以下指示說明如何有效使用 requestHash 欄位:

要索取完整性判定結果時,請完成以下操作:

  • 從正在發生的使用者動作或伺服器要求中,計算所有相關要求參數的摘要 (例如穩定要求序列化的 SHA256)。requestHash 欄位中值的長度上限為 500 個位元組。針對要檢查或保護的動作,在 requestHash 中加入任何重要或相關的應用程式要求資料。由於完整性權杖文字涵蓋 requestHash 欄位,因此過長的值可能會增加要求大小。
  • 將摘要做為 requestHash 欄位提供給 Play Integrity API,並取得完整性權杖。

收到完整性判定結果時,請完成以下操作:

  • 將完整性權杖解碼,然後擷取 requestHash 欄位。
  • 利用與應用程式中相同的方式計算要求摘要,例如穩定要求序列化的 SHA256。
  • 比較應用程式端和伺服器端摘要。如果兩者不相符,代表要求可信度低。

索取完整性判定結果 (隨選)

備妥完整性權杖供應工具後,即可開始向 Google Play 索取完整性判定結果。若要這樣做,請完成下列步驟:

  1. 按照上文取得 StandardIntegrityTokenProvider
  2. 建構 StandardIntegrityTokenRequest,透過 setRequestHash 方法為要保護的使用者動作提供要求雜湊。
  3. 使用完整性權杖供應工具呼叫 request(),提供 StandardIntegrityTokenRequest

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

原生

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

解密並驗證完整性判定結果

您索取完整性判定結果後,Play Integrity API 會提供已加密的回應權杖。您必須在 Google 伺服器上解密完整性權杖,才能取得裝置完整性判定結果。如要啟用這項功能,請完成下列步驟:

  1. 建立服務帳戶 並在連結至應用程式的 Google Cloud 專案中執行
  2. 在應用程式伺服器上,使用 playintegrity 範圍從服務帳戶憑證中擷取存取權杖,然後提出下列要求:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. 讀取 JSON 回應。

產生的酬載是包含完整性判定結果的純文字權杖。

重播攻擊自動防護措施

為防範重播攻擊,Google Play 會自動確保每個完整性權杖都無法重複使用。若嘗試重複解密相同權杖,會產生空白的判定結果。