本頁將說明如何提出標準 API 要求來取得完整性判定結果,您只要使用 Android 5.0 (API 級別 21) 以上版本,即可執行這項操作。每當應用程式發出伺服器呼叫,檢查互動情形是否確實時,您都可以提出標準 API 要求,索取完整性判定結果。
總覽
提出標準要求的程序包含兩個部分:
- 備妥完整性權杖供應工具 (一次性):您需要先呼叫 Integrity API,備妥完整性權杖供應工具,之後才能在需要時取得完整性判定結果。舉例來說,您可以先在應用程式啟動當下或在背景執行這項操作,之後再索取完整性判定結果。
- 索取完整性權杖 (隨選):每當應用程式發出伺服器要求,而您想檢查這項要求是否確實時,都應索取完整性權杖,然後將其傳送至應用程式的後端伺服器,用於解密及驗證。後端伺服器會接著決定要採取的因應措施。
備妥完整性權杖供應工具 (一次性):
- 應用程式使用 Google Cloud 專案編號,呼叫完整性權杖供應工具。
- 應用程式將完整性權杖供應工具儲存在記憶體中,方便進一步用於認證檢查呼叫。
索取完整性權杖 (隨選):
- 應用程式針對需要保護的使用者動作,使用任一適用的雜湊演算法 (例如 SHA256),計算所提要求的雜湊。
- 應用程式索取完整性權杖,傳遞要求雜湊。
- 應用程式從 Play Integrity API 接收已簽署及加密的完整性權杖。
- 應用程式將完整性權杖傳遞至應用程式後端。
- 應用程式後端將權杖傳送至 Google Play 伺服器。Google Play 伺服器解密並驗證判定結果後,將結果傳回應用程式後端。
- 應用程式後端根據權杖酬載中的信號,決定如何繼續操作。
- 應用程式後端將決定結果傳送至應用程式。
備妥完整性權杖供應工具 (一次性)
針對 Google Play 完整性判定結果提出標準要求之前,您必須先備妥完整性權杖供應工具,這也稱為「暖機」程序。如此一來,Google Play 就能靈敏地在裝置上快取部分認證資訊,進而在您索取完整性判定結果時,縮短關鍵路徑上的延遲時間。如要重複執行完整性檢查而不耗用大量資源,可以考慮再次準備權杖供應工具,這樣後續就能取得更即時的完整性判定結果。
您可以在下列情況準備完整性權杖供應工具:
- 應用程式啟動時 (即冷啟動時)。權杖供應工具的準備作業並非同步操作,因此不會影響啟動時間。如果索取完整性判定結果的時間點預計在應用程式啟動後不久,例如使用者登入或玩家加入遊戲時,這個方法就非常實用。
- 應用程式開啟時 (即暖啟動時)。但請注意,每個應用程式例項每分鐘最多只能準備完整性權杖 5 次。
- 如果在背景執行,則可隨時在索取完整性判定結果前準備權杖。
如要準備完整性權杖供應工具,請按照下列指示操作:
- 依照以下範例建立
StandardIntegrityManager
。 - 建構
PrepareIntegrityTokenRequest
,提供 Google Cloud 透過setCloudProjectNumber()
方法建立專案編號 - 使用管理員呼叫
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 索取完整性判定結果。若要這樣做,請完成下列步驟:
- 按照上文取得
StandardIntegrityTokenProvider
。 - 建構
StandardIntegrityTokenRequest
,透過setRequestHash
方法為要保護的使用者動作提供要求雜湊。 - 使用完整性權杖供應工具呼叫
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 伺服器上解密完整性權杖,才能取得裝置完整性判定結果。如要啟用這項功能,請完成下列步驟:
- 建立服務帳戶 並在連結至應用程式的 Google Cloud 專案中執行
在應用程式伺服器上,使用 playintegrity 範圍從服務帳戶憑證中擷取存取權杖,然後提出下列要求:
playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
讀取 JSON 回應。
產生的酬載是包含完整性判定結果的純文字權杖。
重播攻擊自動防護措施
為防範重播攻擊,Google Play 會自動確保每個完整性權杖都無法重複使用。若嘗試重複解密相同權杖,會產生空白的判定結果。