提出標準 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,透過 setCloudProjectNumber() 方法提供 Google Cloud 專案編號。
  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 會自動確保每個完整性權杖都無法重複使用。若嘗試重複解密相同權杖,會產生空白的判定結果。