如何使用 SafetyNet API

Pony Chen
7 min readMay 8, 2016

SafetyNet Attestation 即將在 2023 年 6 月 deprecate,請開發者在時限內 migrate 成 Play Integrity API

SafetyNet API 是 Google 官方提供用來檢查裝置是否可以通過 Android compatibility testing,他會利用裝置本身的硬體與軟體的特徵產生 profile 來做 compatibility check。此 API 不是專門用來檢查裝置是否 root 過,即使沒 root,只是單純使用第三方 ROM,或是使用像中國的一些低價白牌手機,都無法通過驗證。目前沒辦法通過此驗證的手機,都無法使用官方的 Android Pay 進行購物。從 Google Play Service 2.3 以上開始支援 SafetyNet。

整個 SafetyNet 的流程如下:

  1. 建立 Google API Client 物件
  2. 產生 nonce
  3. 傳送 compatibility check request
  4. 獲得 response
  5. 驗證 response

建立 Google API 物件

onCreate 的地方建立 Google API Client 物件,可使用
GoogleApiClient.Builder 來建立此物件

protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(SafetyNet.API)
.addConnectionCallbacks(myMainActivity.this)
.build();
}

產生 nonce

Nonce 是一個在加密通信只能使用一次的數字,通常為一個亂數,以避免重送攻擊。可以由 Client 自己建立此亂數,或是從 Server 取得。
在 SafetyNet API 中,nonce 的長度至少需要 16 bytes

傳送 compatibility check request

byte[] nonce = getRequestNonce(); // Should be at least 16 bytes in length.
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {

@Override
public void onResult(SafetyNetApi.AttestationResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
// Indicates communication with the service was successful.
// result.getJwsResult() contains the result data
} else {
// An error occurred while communicating with the service
}
}
});

獲得 response

若發送成功,可以透過 SafetyNetApi.AttestationResultgetJwsResult() 得到驗證的結果,是一個 String 物件,格式為 JSON Web Signature (JWS)。他是一個使用 . 將三個部分串起來的字串,格式為:

BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)

其中我們需要得到的資訊在他的 payload 的部分,取出後使用 Base64 URL_Safe decode 後即可得到我們所需要的 JSON 資料,轉換完的範例如下:

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

如果 ctsProfileMatchtrue,則代表通過 Android compatibility check。假設 getJwsResult() 回傳的是 null,或其結果中含有 error: 的屬性,則必須重新進行驗證

驗證 response

取得驗證結果後,還必須要確認剛剛得到的結果是由 SafetyNet 所回應,以及與提供的資料相符。此部分可以透過 client 自行驗證,或經由 server 處理。

驗證訊息是由 SafetyNet 所回應的方法有兩種,土炮的做法是:

  • 從 JWS message 中取出 SSL certificate chain
  • 驗證 certificate 是由 attest.android.com 所核發
  • 使用 certificate 來驗證 JWS message 的簽章

但,Google 有提供 Android Device Verification API 來驗證 SafetyNet 的回傳結果,需先至 Google Developers Console 將此 API 開啟並取得 API Key,方法如下:

  1. 進入 Google Developers Console
  2. 選擇你的 project
  3. 展開左側的 APIs & auth 後點選 APIs,確保你使用到的 API 都處在 On 的狀態
  4. Browse APIs 列表中,找到 Android Device Verification API 並將它開啟
  5. 在左方展開後的 APIs & auth 中點選 Credentials,將 API KEY 記錄下來 (注意,請勿使用 Android Key)

開啟此功能後,就可以由 client 本身或 server 進行驗證

使用 Android Device Verfication 的做法:

1.建立包含 JWS message 的 JSON message

{ "signedAttestation": "<output of getJwsResult()>" }

2.發送 Content-Type 為 application/json 的 HTTP POST 至下列網址:

https://www.googleapis.com/androidcheck/v1/attestations/verify?key=<your API key>

3.若驗證成功,會得到以下的回應:

{ "isValidSignature": true }

最後,此 API 並不會檢查資料是否與提供的相符。要驗證這件事情,則可以從 Noncetimestamppackage nameSHA-256 確認

更多的技術細節,可以參考以下網址:
[1] Checking Device Compatibility with SafetyNet (​https://developer.android.com/intl/zh-tw/training/safetynet/index.html)
[2] SafetyNet: Google’s tamper detection (https://koz.io/inside-safetynet/)
[3] Inside SafetyNet — part 2 (https://koz.io/inside-safetynet-2/)
[4] Using the SafetyNet API (https://www.cigital.com/blog/using-safetynet-api/)

--

--