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 的流程如下:
- 建立 Google API Client 物件
- 產生 nonce
- 傳送 compatibility check request
- 獲得 response
- 驗證 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.AttestationResult 的 getJwsResult() 得到驗證的結果,是一個 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,
}
如果 ctsProfileMatch 為 true,則代表通過 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,方法如下:
- 進入 Google Developers Console
- 選擇你的 project
- 展開左側的 APIs & auth 後點選 APIs,確保你使用到的 API 都處在 On 的狀態
- 在 Browse APIs 列表中,找到 Android Device Verification API 並將它開啟
- 在左方展開後的 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 並不會檢查資料是否與提供的相符。要驗證這件事情,則可以從 Nonce、timestamp、package name 與 SHA-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/)