SafetyNet Attestation API

SafetyNet Attestation API 是一款防濫用 API,可讓應用程式開發人員評估執行應用程式的 Android 裝置。這個 API 應做為濫用行為偵測系統的一環,協助您判斷伺服器的互動對象是否為在正版 Android 裝置上執行的正版應用程式。

SafetyNet Attestation API 提供經過加密編譯的認證機制,用於評估裝置完整性。為了確立認證,API 會檢查裝置的軟硬體環境,找出完整性問題,並將相關數據與已獲准 Android 裝置的參照資料相比較。產生的認證會繫結至呼叫端應用程式提供的 Nonce。這份認證也會包含產生時間戳記,以及提出要求應用程式的中繼資料。

這款 API 適用於以下用途:

  • 做為獨立的反濫用或應用程式安全性機制。請根據我們發布的應用程式安全性最佳做法,以及特定產品的反濫用信號套件,搭配使用這款 API。
  • 在裝置離線時運作。這種情況下,API 會傳回錯誤。
  • 直接在發出呼叫的應用程式中解讀回應。將所有反濫用決策邏輯移至您控管的伺服器中。
  • 提供有關系統修改作業的詳細信號。此 API 提供的是布林值,表示不同程度的系統完整性。
  • 包含應用程式專屬用途的信號,例如裝置 ID、GPS 模擬狀態,以及螢幕鎖定狀態。
  • 取代或實作嚴格的 DRM 檢查。
  • 單純檢查裝置是否已啟用 Root 權限,因為這款 API 的設計宗旨是檢查裝置的整體完整性。

總覽

以下是 SafetyNet Attestation API 採取的工作流程:

  1. SafetyNet Attestation API 接收來自應用程式的呼叫。這個呼叫包含 Nonce。
  2. SafetyNet Attestation 服務評估執行階段環境,並要求 Google 伺服器提供已簽署的評估結果認證。
  3. Google 伺服器將已簽署的認證傳送至裝置上的 SafetyNet Attestation 服務。
  4. SafetyNet Attestation 服務將這份已簽署的認證傳回至您的應用程式。
  5. 應用程式將已簽署的認證轉送至您的伺服器。
  6. 伺服器驗證回應,並據此制定反濫用決策,然後將相關發現傳送至應用程式。

圖 1 是這項程序的流程圖:

圖 1. SafetyNet Attestation API 通訊協定

注意:其他說明文件和檢查清單

在初始化、設定和啟用 SafetyNet Attestation API 的整個過程中,除了這份主要說明文件外,也請留意並遵守下列建議:

取得 API 金鑰

您必須使用 API 金鑰,才能呼叫 SafetyNet Attestation API 的方法。不過,由於 SafetyNet Attestation API 已淘汰,因此您無法再要求新的 API 金鑰。

API 配額與監控

每項專案用於呼叫 SafetyNet Attestation API 的預設配額是每天 10,000 個要求,這適用於您的所有使用者族群。如要提出更多完整性要求,請改用 Play Integrity API,並依照 Play Integrity API 說明文件中的指示要求提高配額。請注意,配額要求的處理程序需要數個工作天,因此建議您設定配額監控及快訊功能,以免發生緊急情況。

注意:無論專案佈建的配額為何,每個應用程式執行個體每分鐘最多可以提出 5 個要求。如果超過此上限,在那一分鐘內提出的所有後續要求都會傳回錯誤。

實作應用程式的重試機制時,請將此行為納入考量。

查看 Google Play 服務版本

使用 SafetyNet Attestation API 之前,您「必須」確保使用者裝置上安裝正確的 Google Play 服務版本。如果安裝錯誤的版本,應用程式可能會在呼叫 API 後停止回應。如果應用程式偵測到安裝的版本有誤,您應要求使用者更新裝置上的 Google Play 服務應用程式

如要檢查安裝的 Google Play 服務版本是否與您目前使用的 Android SDK 版本相容,請呼叫 isGooglePlayServicesAvailable() 方法,如以下程式碼片段所示:

if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
        == ConnectionResult.SUCCESS) {
  // The SafetyNet Attestation API is available.
} else {
  // Prompt user to update Google Play services.
}

在搭載 Google Play 服務 13.0 以上版本的裝置上,SafetyNet Attestation API 也支援受限應用程式的 API 金鑰。這項功能可減少風險,避免在意外或未經授權情況下使用配額受限的 API 金鑰。如要使用這項選用功能,請確認裝置上的 Google Play 服務至少需為 13.0 版,如以下程式碼片段所示:

if (GoogleApiAvailability.getInstance()
    .isGooglePlayServicesAvailable(context, 13000000) ==
    ConnectionResult.SUCCESS) {
  // The SafetyNet Attestation API is available.
} else {
  // Prompt user to update Google Play Services.
}

要求 SafetyNet 認證

您在 Google API 控制台中取得適用於 Android Device Verification API 的 API 金鑰後,應用程式即可使用 SafetyNet Attestation 服務。若要這樣做,請完成下列步驟:

  1. 取得 Nonce。
  2. 要求 SafetyNet 認證。
  3. 將回應轉移至您的伺服器。
  4. 使用伺服器上的回應以及其他反濫用信號,控管應用程式行為。

為了讓應用程式持續回應,請在應用程式的主要執行緒外執行這些步驟。如要進一步瞭解如何建立不同的執行緒,請參閱「傳送多項作業至多個執行緒」。

建議您執行這項檢查,確保應用程式中的所有重要操作 (包括登入、購買活動和取得新的應用程式內商品等) 安全無虞。不過,呼叫 SafetyNet Attestation API 會導致延遲時間、行動數據用量和電池用量增加,因此在安全性和可用性間取得平衡十分重要。舉例來說,您可以選擇在登入時要求 SafetyNet 認證,然後最多每 30 分鐘重新檢查一次。或者,也可以讓伺服器決定應用程式要求認證的時機,這樣攻擊者就會更難以預測檢查時間。

取得 Nonce

呼叫 SafetyNet Attestation API 時,您必須傳入 Nonce。產生的認證會包含此 Nonce,方便您判斷認證屬於您的 API 呼叫,而非由攻擊者重播而得。

用於 SafetyNet 要求的 Nonce 長度至少應為 16 個位元組。Nonce 應具有可變性,確保不會重複使用同一個 Nonce。最佳做法是從傳送至伺服器的資料中導出部分的 Nonce。例如,將使用者名稱的雜湊與要求時間戳記串連起來,藉此形成 Nonce。

重要事項:請在 Nonce 中盡量加入資料。如此一來,攻擊者就比較難執行重播攻擊。例如,從使用者名稱導出 Nonce,可限制向同一帳戶發動重播攻擊。然而,若是從購買活動的所有詳細資料導出 Nonce,會將 API 的回應訊息限制為僅用於該購買活動。

收到來自 API 的已簽署回應後,如果您已根據傳送至伺服器的其餘訊息重新建構 Nonce,請一律將其與該簽署回應中的 Nonce 相比較。這項檢查可確保攻擊者無法將從良好裝置取得的已簽署認證,重複用於其他惡意要求。

如要進一步瞭解如何使用加密編譯函式,請參閱「使用加密編譯」指南。

要求認證

當您與 Google Play 服務連線並建立 Nonce 後,就可以準備提出 SafetyNet 認證要求了。您的要求可能不會立即獲得回應,因此建議您設定回呼事件監聽器,以便處理該服務發出的回應。如需事件監聽器範例,請參閱以下程式碼片段:

Kotlin

SafetyNet.getClient(this).attest(nonce, API_KEY)
    .addOnSuccessListener(this) {
        // Indicates communication with the service was successful.
        // Use response.getJwsResult() to get the result data.
    }
    .addOnFailureListener(this) { e ->
        // An error occurred while communicating with the service.
        if (e is ApiException) {
            // An error with the Google Play services API contains some
            // additional details.
            val apiException = e as ApiException

            // You can retrieve the status code using the
            // apiException.statusCode property.
        } else {
            // A different, unknown type of error occurred.
            Log.d(FragmentActivity.TAG, "Error: " + e.message)
        }
    }

Java

// The nonce should be at least 16 bytes in length.
// You must generate the value of API_KEY in the Google APIs dashboard.
SafetyNet.getClient(this).attest(nonce, API_KEY)
    .addOnSuccessListener(this,
        new OnSuccessListener<SafetyNetApi.AttestationResponse>() {
            @Override
            public void onSuccess(SafetyNetApi.AttestationResponse response) {
                // Indicates communication with the service was successful.
                // Use response.getJwsResult() to get the result data.
            }
        })
    .addOnFailureListener(this, new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception e) {
            // An error occurred while communicating with the service.
            if (e instanceof ApiException) {
                // An error with the Google Play services API contains some
                // additional details.
                ApiException apiException = (ApiException) e;
                // You can retrieve the status code using the
                // apiException.getStatusCode() method.
            } else {
                // A different, unknown type of error occurred.
                Log.d(TAG, "Error: " + e.getMessage());
            }
        }
    });

onSuccess() 方法表示與服務通訊成功,但不會指出裝置是否通過 SafetyNet 認證。下一節將討論如何解讀認證結果並驗證其完整性。

將 SafetyNet 認證回應轉移至您的伺服器

您的應用程式與 SafetyNet 通訊時,該服務會提供包含 SafetyNet 認證結果的回應,以及其他有助您驗證訊息完整性的資訊。此結果會以 SafetyNetApi.AttestationResponse 物件的形式提供。請使用這個物件的 getJwsResult() 方法,取得有關要求的資料。回應會格式化為 JSON Web Signature (JWS)

接著請將 JWS 物件傳回至您的伺服器,以利驗證及使用。

使用伺服器上的 SafetyNet 認證回應

請參閱以下的 JWS 摘錄,瞭解酬載資料的格式和範例內容:

{
  "timestampMs": 9860437986543,
  "nonce": "R2Rra24fVm5xa2Mg",
  "apkPackageName": "com.package.name.of.requesting.app",
  "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                  certificate used to sign requesting app"],
  "ctsProfileMatch": true,
  "basicIntegrity": true,
  "evaluationType": "BASIC",
  "deprecationInformation": "..."
}

已簽署認證的酬載通常包含下列欄位:

回應時間戳記

  • timestampMs:Google 伺服器產生 JWS 回應訊息的時間,以自 Unix 紀元以來的毫秒數表示。

發出呼叫的應用程式所供資料

  • nonce:由發出呼叫的應用程式傳送至 API 的單次使用權杖。

發出呼叫的應用程式相關資料

  • apkPackageName:發出呼叫的應用程式的套件名稱。
  • apkCertificateDigestSha256:發出呼叫的應用程式的簽署憑證 SHA-256 雜湊 (以 Base-64 編碼表示)。

完整性判定結果

  • ctsProfileMatch:以較嚴格的標準判定裝置完整性。如果 ctsProfileMatch 的值為 true,表示不論是執行應用程式的裝置,還是通過 Android 相容性測試、且經核准為 Google 認證的 Android 裝置,兩者的設定檔都彼此相符。
  • basicIntegrity:以較寬鬆的標準判定裝置完整性。如果只有 basicIntegrity 的值為 true,表示執行應用程式的裝置可能未遭到竄改。不過,該裝置不一定已通過 Android 相容性測試。

    如要進一步瞭解 Android 相容性測試,請參閱「設計 Android 裝置」、「Android 相容性」和「Compatibility Testing Suite (CTS)」。

淘汰資訊

  • deprecationInformation:此字串中的資訊會向開發人員說明 SafetyNet Attestation API 的淘汰事宜。

選填欄位

  • error:與當前 API 要求相關的編碼錯誤資訊。
  • advice:有關如何讓裝置恢復良好狀態的建議。
  • evaluationType:促成目前 API 回應的評估類型。

可能的完整性判定結果

JWS 訊息包含 ctsProfileMatchbasicIntegrity 這兩個參數,用於表示裝置相容性的檢查結果。如表 1 所示,執行應用程式的裝置處於何種狀態,可能會對每個參數的值造成影響:

表 1. 舉例說明裝置狀態可能如何影響 basicIntegrityctsProfileMatch 的值

裝置狀態 ctsProfileMatch 的值 basicIntegrity 的值
正版裝置、經認證且通過 CTS 測試 true true
裝置經過認證,且系統啟動載入程式已解鎖 false true
正版裝置,但未經認證,例如製造商未提出認證申請 false true
裝置搭載自訂 ROM (未啟用 Root 權限) false true
模擬器 false false
無裝置 (例如只有模擬指令碼的通訊協定) false false
裝置出現系統完整性遭破壞的跡象,其中一項可能是已啟用 Root 權限 false false
裝置有正在受到其他攻擊的跡象,例如掛接 API false false

錯誤情況

JWS 訊息也可能顯示以下幾種錯誤情況:

  • null 結果表示對服務的呼叫未順利完成。
  • JWS 中的錯誤參數會指出有問題發生,例如網路錯誤或攻擊者偽裝的錯誤。這些錯誤多半都是暫時的,如果您再次呼叫服務,這類錯誤應該就會消失。建議您重試幾次,每次重試時增加延遲時間。
  • 如果裝置遭到竄改 (也就是在回應中將 basicIntegrity 設為 false),判定結果可能就不會包含提出呼叫的應用程式資料,例如 apkPackageNameapkCertificateDigestSha256。當系統無法準確判斷提出呼叫的應用程式時,就會發生這種情況。

如果系統在經過簽署認證後回報錯誤,該如何處理?

  • 重試。正版裝置發生的錯誤屬於暫時性錯誤,如果您再次呼叫服務,應該就不會出現這些錯誤。
  • 確認在受影響的裝置上,應用程式每分鐘呼叫 API 的次數不超過 5 次,且專案的 API 配額尚未用盡。
  • 假設這種情形可能是攻擊者為了掩飾自身活動,而刻意觸發的錯誤。

協助通過日後檢查的 advice 參數

如果有 advice 參數的話,就可以提供資訊,協助說明 SafetyNet Attestation API 為何在特定結果中將 ctsProfileMatchbasicIntegrity 設為 false。該參數的值包含字串清單,如以下範例所示:

{"advice": "LOCK_BOOTLOADER,RESTORE_TO_FACTORY_ROM"}

在應用程式中,您可以將 advice 參數中的值轉譯為適合使用者閱讀的訊息,幫助他們通過日後的 SafetyNet 認證,如以下清單所示:

LOCK_BOOTLOADER
使用者應鎖定裝置的系統啟動載入程式。
RESTORE_TO_FACTORY_ROM
使用者應將裝置還原為清空的原廠 ROM。

評估類型

每當出現 ctsProfileMatchbasicIntegrity 的判定結果時,就會產生 evaluationType 參數。

這項參數會提供評估類型資訊,評估類型的用途是運算特定回應中的 ctsProfileMatchbasicIntegrity 等欄位。該參數的值包含字串清單,如以下範例所示:

{"evaluationType": "BASIC,HARDWARE_BACKED"}

目前,可能的值包括:

BASIC
使用一般的評估和參考資料。
HARDWARE_BACKED
使用經過硬體備份的安全防護功能,包括金鑰認證這類功能,適用於搭載 Android 8.0 (API 級別 26) 以上版本的裝置。

在應用程式中,如果 evaluationType 參數中含有 HARDWARE_BACKED,您可以將其視為一種指標,表示裝置完整性的評估標準較為嚴格。

注意:除非應用程式已採用 ctsProfileMatch 判定結果,且需要最高程度的裝置完整性保證,即使會對使用者族群造成限制也是如此,否則我們不建議參考 evaluationType 參數。在多數情況下,您應持續參考 basicIntegrityctsProfileMatch 判定結果來偵測濫用行為。系統產生這些判定結果時,採納的是經硬體備份的安全防護功能 (如適用)。

如果您決定參考 evaluationType 參數中的特定值,建議您在應用程式中實作重試機制,以免發生暫時性錯誤。

驗證 SafetyNet 認證回應

您應採取行動,確保 SafetyNet 認證回應確實來自 SafetyNet 服務,且包含與要求相符的資料。

如要驗證 JWS 訊息的來源,請完成下列步驟:

  1. 從 JWS 訊息中擷取 SSL 憑證鏈結。
  2. 驗證 SSL 憑證鏈結,並使用 SSL 主機名稱比對功能,驗證分葉憑證已順利核發給主機名稱 attest.android.com
  3. 使用憑證驗證 JWS 訊息的簽名。
  4. 檢查 JWS 訊息的資料,確認其與原始要求中的資料相符。特別是,請確認時間戳記已完成驗證,且應用程式簽署憑證的 Nonce、套件名稱和雜湊均符合預期值。

驗證 JWS 陳述式時,您應採用標準的加密編譯解決方案,例如 GitHub 提供的 android-play-safetynet 範例 API 用法

在初始測試和開發期間 (而非正式推出階段),您可以呼叫線上 API 來驗證 JWS 陳述式的簽名。在 GitHub 提供的 android-play-safetynet 範例 API 用法中,您也可以看到這個程序的運作方式。請注意,線上驗證 API 僅供初期測試之用,且每天的固定要求配額為 10,000 次。

重要事項:使用線上驗證 API 只會驗證 JWS 訊息是否已由 SafetyNet Attestation API 的伺服器簽署。這個線上 API 無法驗證酬載中的欄位是否與您服務預期的值相符。

針對意外情況做好規畫

建議您根據使用情況做好規畫,將變更和服務中斷情形納入考量。

API 變更
您隨時可能會在判定結果中看到新的 (實驗性) 欄位。請確保這些額外欄位不會對剖析器或使用邏輯造成損壞。尤其是,在我們透過 SafetyNet API 客戶郵寄清單公告推出實驗性欄位前,請勿參考這些欄位。
SafetyNet Attestation API 已停止運作

萬一 SafetyNet Attestation API 無法使用,我們強烈建議這個 API 的使用者建構伺服器端功能,以動態方式控管要如何參考此 API 的可用性品質和相關回應。

典型策略應包括:能夠動態指示應用程式停止呼叫此 API 以及基於裝置和使用者的許可清單,以便忽略特定裝置和使用者類別的 SafetyNet Attestation API 結果。

程式碼範例

如需有關使用 SafetyNet API 的其他指南,請參閱 GitHub 上的程式碼範例

公告清單

強烈建議您加入 SafetyNet API 客戶郵寄清單,以便接收 SafetyNet Attestation API 的最新消息。

意見回饋

歡迎對此 API 提供意見回饋。我們會聽取您的意見,優先開發這個 API 的新功能。

瞭解詳情

如要進一步瞭解使用 SafetyNet Attestation API 的最佳做法,請參閱下列連結:

附加服務條款

存取或使用 SafetyNet API,即表示您同意《Google API 服務條款》和以下《附加條款》。存取 API 前,請詳閱並瞭解所有適用的條款和政策。

SafetyNet 服務條款

就像是透過實地觀察收集而來的大量資料一樣,難免也可能會出現偽陽性和偽陰性的判定結果。我們會盡己所知提供資料,充分測試偵測機制以確保準確度,並持續努力改善這些方法,確認這些方法能繼續提供準確結果。

您同意遵守所有適用法律、法規和第三方權利規定 (包括但不限於有關匯入或匯出資料/軟體的法律、隱私權及當地法律)。您不得使用 API 鼓吹或宣傳非法活動,亦不得違反第三方權利,以及與 Google (或其關係企業) 簽訂的任何其他服務條款。

您知悉並瞭解 SafetyNet API 運作時會收集軟硬體資訊 (例如裝置和應用程式資料) 與 SafetyNet 認證結果,並將收集到的資料傳送給 Google 進行分析。根據《Google API 服務條款》第 3(d) 節規定,一旦使用這些 API,即表示您同意負責向使用者提供任何必要通知,說明系統會收集上述資料並提供給 Google,也會徵求使用者同意。

SafetyNet Attestation 資料安全性

Google Play 提供了資料安全性專區,可讓開發人員揭露應用程式的資料收集、分享和安全性做法。為確保您滿足資料安全性專區的相關要求,您可以參考下方的資訊,瞭解 SafetyNet Attestation API 如何處理資料:

做法類型 SafetyNet Attestation API 如何採用該做法
收集到的使用情況資料
  • 套件名稱
  • 應用程式簽署憑證
  • Google Play 服務產生的裝置認證權杖
資料收集目的 收集到的資料會用於驗證應用程式完整性和裝置完整性。
資料加密 資料未加密。
資料分享 資料不會轉移給任何第三方。
資料刪除 過了固定的保留期限後,系統會刪除資料。

為了完成資料揭露聲明,您可以利用 Android 的資料類型指南,判斷哪種資料類型最符合所收集的資料。揭露資料時,也請務必說明特定應用程式如何分享及使用收集到的資料。

雖然我們的目標是在為您提供支援時盡可能公開透明,但是,您仍須全權決定如何回覆 Google Play 的資料安全性專區表單,說明應用程式的使用者資料收集、分享和安全性做法。我們無法代您回答。