SafetyNet Attestation API

SafetyNet Attestation API は、アプリが実行されている Android デバイスをアプリ デベロッパーが評価するための不正利用防止 API です。この API は不正利用検出システムの一部として、サーバーとやり取りしているのが正規の Android デバイスで実行されている正規のアプリかどうかを判断するために使用します。

SafetyNet Attestation API はデバイスの完全性を評価し、暗号技術を使って署名された構成証明を提供します。構成証明を作成するために、API はデバイスのソフトウェアとハードウェアの環境を検査して、完全性の問題がないか調べ、承認済みの Android デバイスのリファレンス データと比較します。生成される構成証明は、呼び出し側のアプリから与えられるノンスに紐付けされます。また、この構成証明には生成時のタイムスタンプと、リクエストするアプリに関するメタデータも含まれています。

以下のユースケースはこの API の設計で意図したものではありません

  • スタンドアロンの不正利用防止あるいはアプリ セキュリティの仕組みとして機能させる。公開されているアプリ セキュリティのおすすめの方法、およびプロダクト固有の不正利用防止情報と組み合わせて利用してください。
  • デバイスがインターネットに接続されていないときに機能させる。この場合、API はエラーを返します。
  • レスポンスを呼び出し元のアプリで直接解釈させる。すべての不正利用防止の判断ロジックをご自身の管理下にあるサーバーに移動してください。
  • システムの変更に関する詳細な情報を得る。この API から取得できるのは、さまざまなレベルのシステム完全性をブール値で表したものです。
  • デバイス固有 ID、GPS エミュレーション ステータス、画面ロックのステータスなど、アプリ固有のユースケースに関する情報を含める。
  • 強力な DRM 検査を置き換える、または実装する。
  • デバイスがルート権限取得されているかどうかのみをチェックさせる。この API はデバイスの全体的な完全性をチェックするように設計されています。

概要

SafetyNet Attestation API では、次のワークフローが使用されます。

  1. SafetyNet Attestation API がアプリからの呼び出しを受けます。この呼び出しにはノンスが含まれます。
  2. SafetyNet Attestation サービスはランタイム環境を評価し、Google のサーバーに評価結果の署名済み構成証明をリクエストします。
  3. Google のサーバーは署名済み構成証明をデバイスの SafetyNet Attestation サービスに送信します。
  4. SafetyNet Attestation サービスは、この署名済み構成証明をアプリに返します。
  5. アプリは署名済み構成証明をサーバーに転送します。
  6. このサーバーでレスポンスを検証し、不正利用防止の判断に使用します。サーバーが調査結果をアプリに伝えます。

このプロセスを図 1 に示します。

図 1:SafetyNet Attestation API プロトコル

注: 追加のドキュメントとチェックリスト

SafetyNet Attestation API の初期化、設定、有効化では、このメインのドキュメントに加えて、以下のアドバイスを念頭に置いてください。

API キーを取得する

SafetyNet Attestation API のメソッドを呼び出すには、API キーを使用する必要があります。SafetyNet Attestation API は非推奨であるため、新しい API キーはリクエストできません。

API の割り当てとモニタリング

SafetyNet Attestation API の呼び出しのプロジェクトあたりのデフォルトの割り当ては、ユーザーベース全体で 1 日あたり 10,000 リクエストです。完全性リクエストの数を追加するには、Play Integrity API に移行し、Play Integrity API のドキュメントに記載されている手順に沿って、割り当ての増加をリクエストします。割り当てのリクエストの処理には数営業日かかるため、緊急事態の発生を避けるために、割り当てモニタリングと割り当てアラートをセットアップすることをおすすめします。

注: プロジェクトにプロビジョニングされた割り当てにかかわらず、個々のアプリ インスタンスのリクエストは 1 分あたり最大 5 件に制限されます。この上限を超えると、その 1 分間の残りのリクエストはすべてエラーを返します。

アプリの再試行メカニズムを実装する際は、この動作を念頭に置いてください。

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 開発者サービス v13.0 以降を搭載するデバイスでは、SafetyNet Attestation API はアプリ制限のある API キーもサポートしています。この機能を使用すると、割り当て制限のある API キーが誤って使用されるリスク、および不正に使用されるリスクが軽減されます。このオプション機能を使用するには、次のコード スニペットに示すように、デバイスの Google Play 開発者サービスのバージョンが v13.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 Console で Android Device Verification API 用の有効な API キーを取得すると、アプリで SafetyNet Attestation サービスを使用できるようになります。そのための手順は次のとおりです。

  1. nonce を取得します。
  2. SafetyNet 構成証明をリクエストします。
  3. レスポンスをサーバーに転送します。
  4. サーバー上のレスポンスと他の不正利用防止情報を使用して、アプリの動作を管理します。

アプリの応答性を維持するには、アプリのメイン実行スレッドの外でこれらの手順を実行してください。別の実行スレッドを作成する方法については、複数スレッドへの処理の分散をご覧ください。

アプリ内でのログイン、購入イベント、新しいアプリ内アイテムの取得などの重要なすべてのアクションを保護するために、このチェックを行う必要があります。ただし、SafetyNet Attestation API を呼び出すと、レイテンシ、モバイルデータ使用量、バッテリー使用量が増加するため、セキュリティと使いやすさのバランスをとることが重要です。たとえば、ログイン時に SafetyNet 構成証明をリクエストし、30 分ごとに再確認することもできます。また、アプリが構成証明を要求するタイミングをサーバーに決定させて、攻撃者がチェックのタイミングを予測しにくくすることもできます。

ノンスを取得する

SafetyNet Attestation API を呼び出すときは、ノンスを渡す必要があります。結果として得られる構成証明にはこのノンスが含まれており、構成証明がこの API 呼び出しの結果であって、攻撃者によるリプレイではないことがわかります。

SafetyNet リクエストで使用されるノンスは 16 バイト以上である必要があります。ノンスに変化を持たせ、同じノンスを繰り返し使用しないようにします。サーバーに送信されたデータをノンスの一部に使用することをおすすめします。たとえば、ユーザー名のハッシュをリクエストのタイムスタンプと連結してノンスを作成する方法があります。

重要:ノンスにはできる限り多くのデータを入れてください。そうすることで、リプレイ攻撃が難しくなります。たとえば、ユーザー名からノンスを作成すると、リプレイ攻撃は同じアカウントに制限されます。ただし、購入イベントの詳細すべてを使ってノンスを作成すると、API のレスポンス メッセージはその購入イベント以外で使用できなくなります。

API から署名済みのレスポンスを受け取ったら、署名済みのレスポンスのノンスと、サーバーに送信されたメッセージの残りから再現したノンスの比較を常に行います。このチェックにより、攻撃者が正規のデバイスから収集した署名済みの構成証明を再利用して、悪意のあるリクエストを作成することができなくなります。

暗号機能の使用方法の詳細については、暗号技術の使用方法に関するガイドをご覧ください。

構成証明をリクエストする

Google Play 開発者サービスへの接続が確立され、ノンスが作成されると、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: JWS レスポンス メッセージが Google のサーバーによって生成された時刻を示す UNIX エポックからの経過時間(ミリ秒単位)。

呼び出し元のアプリから提供されたデータ

  • nonce: 呼び出し元のアプリが API に渡した使い捨てトークン。

呼び出し元のアプリに関するデータ

  • apkPackageName: 呼び出し元のアプリのパッケージ名。
  • apkCertificateDigestSha256: 呼び出し元のアプリの署名構成証明に SHA-256 ハッシュをかけて Base-64 エンコードしたもの。

完全性判定の結果

  • ctsProfileMatch: デバイスの完全性に関する厳密な判定結果。ctsProfileMatch の値が true の場合、アプリを実行しているデバイスのプロファイルは、Android 互換性テストに合格し、Google 認定済み Android デバイスとして承認されたデバイスのプロファイルと一致しています。
  • basicIntegrity: デバイスの完全性に対する緩やかな判定結果。basicIntegrity の値のみが true の場合、アプリを実行しているデバイスは高い可能性で改ざんされていません。ただし、デバイスが Android の互換性テストに合格していない可能性はあります。

    Android 互換性テストの詳細については、Android デバイスの設計互換性テストスイート(CTS)をご覧ください。

サポート終了に関する情報

  • deprecationInformation: SafetyNet Attestation API のサポート終了に関するデベロッパー向けの情報を含む文字列。

省略可能項目

  • error: 現在の API リクエストに関連するエラー情報をエンコードしたもの。
  • advice: デバイスを適切な状態に戻す方法の提案。
  • evaluationType: 現在の API レスポンスに影響を与えた測定のタイプ。

考えられる完全性判定の結果

JWS メッセージには、デバイスの互換性チェックの結果を表す 2 つのパラメータ(ctsProfileMatchbasicIntegrity)が含まれています。表 1 に示すように、アプリを実行しているデバイスのステータスは、それぞれのパラメータの値に影響します。

表 1.basicIntegrityctsProfileMatch の値に影響するデバイス ステータスの例

デバイス ステータス ctsProfileMatch の値 basicIntegrity の値
CTS に合格した認証済みの正規デバイス true true
ブートローダーがロック解除されている認証済みデバイス false true
正規であるものの認証されていないデバイス(メーカーが認証を申請していないなど) false true
カスタム ROM を搭載したデバイス(ルート権限取得されていないデバイス) false true
エミュレータ false false
デバイスがない(プロトコルをエミュレートするスクリプトなど) false false
システムの完全性が侵害されている兆候(ルート権限取得など) false false
その他のアクティブな攻撃の兆候(API フックなど) false false

エラーとなるケース

JWS メッセージには、次のようなエラー状態が示される場合もあります。

  • 結果が null の場合、サービスへの呼び出しが正常に完了しなかったことを示します。
  • JWS の error パラメータは、ネットワーク エラーや攻撃者が偽造したエラーなどの問題が発生したことを示します。ほとんどのエラーは一時的なもので、もう一度サービスを呼び出すとエラーはなくなります。時間間隔を広げながら再試行することをおすすめします。
  • デバイスが改ざんされている場合、つまり、レスポンスの basicIntegrity が false に設定されている場合、apkPackageNameapkCertificateDigestSha256 など、呼び出し元のアプリに関するデータは判定結果に含まれない場合があります。これはシステムが呼び出し元のアプリを確実に判断できない場合に発生します。

署名済み構成証明がエラーを示している場合の対処方法

  • 再試行します。正規のデバイスで発生したエラーは一時的なものであり、再度サービスを呼び出すとなくなります。
  • 対象のデバイスでアプリが 1 分間に 5 回を超えて API を呼び出していないこと、プロジェクトの API 割り当てが残っていることを確認します。
  • 意図的にエラーケースをトリガーしてアクティビティを偽装している攻撃者だと見なします

将来のチェックに合格するためのアドバイス

advice パラメータは存在する場合、SafetyNet Attestation API が特定の結果で ctsProfileMatch または basicIntegrity のいずれかを false に設定する理由を説明した情報を示します。このパラメータの値には、次のような文字列のリストが入っています。

{"advice": "LOCK_BOOTLOADER,RESTORE_TO_FACTORY_ROM"}

アプリでは、advice パラメータの値を次に示すようなユーザーが理解しやすいメッセージに翻訳して、将来の SafetyNet 構成証明に合格するためのヒントをユーザーに提示できます。

LOCK_BOOTLOADER
デバイスのブートローダーをロックする必要があります。
RESTORE_TO_FACTORY_ROM
デバイスの ROM を工場出荷時の状態に戻す必要があります。

評価のタイプ

evaluationType パラメータは、ctsProfileMatchbasicIntegrity の判定結果がある場合は常に存在します。

このパラメータは、特定のレスポンスの ctsProfileMatchbasicIntegrity などのフィールドの計算に使用される、測定の種類に関する情報を提供します。パラメータの値には次の例のように文字列のリストが含まれます。

{"evaluationType": "BASIC,HARDWARE_BACKED"}

現在、有効な値は次のとおりです。

BASIC
一般的な測定とリファレンス データが使用されました。
HARDWARE_BACKED
ハードウェア格納型のセキュリティ機能が使用されました。これは鍵構成証明などの機能が該当します。Android 8.0(API レベル 26)以降を搭載して出荷されるデバイスでサポートされています

アプリで evaluationType パラメータに HARDWARE_BACKED が存在する場合、デバイスの完全性がより高く評価されていると見なせます。

注: evaluationType パラメータは、すでに ctsProfileMatch の判定結果を使用しており、ユーザーベースが制限されたとしてもデバイスの完全性に対する最高レベルの保証を必要とするアプリでのみ利用することをおすすめします。ほとんどの場合、不正使用の検出には引き続き basicIntegrityctsProfileMatch の判定結果を利用する必要があります。該当する場合には、これらの判定結果にはハードウェア格納型セキュリティ機能がすでに組み込まれています。

evaluationType パラメータに特定の値が存在することに依存する場合は、一時的なエラーに備えてアプリへの再試行メカニズムの実装を検討してください。

SafetyNet 構成証明のレスポンスを検証する

いくつかの手順を踏んで、SafetyNet 構成証明のレスポンスが実際に SafetyNet サービスから送信され、リクエストに一致するデータが含まれていることを確認する必要があります。

次の手順を実施して、JWS メッセージの送信元を確認します。

  1. SSL 証明書チェーンを JWS メッセージから抽出します。
  2. SSL 証明書チェーンを検証し、SSL のホスト名マッチングを使用して、リーフ証明書がホスト名 attest.android.com に対して発行されていることを確認します。
  3. 証明書を使用して、JWS メッセージの署名を検証します。
  4. JWS メッセージのデータが元のリクエスト内のデータと一致していることを確認します。具体的には、タイムスタンプが検証済みで、ノンス、パッケージ名、アプリの署名証明書のハッシュが想定どおりの値になっていることを確認します。

GitHub の android-play-safetynet のサンプル API の使用法にあるような標準的な暗号ソリューションを使用して、JWS ステートメントを検証する必要があります。

最初のテストと開発(製品版以外)では、オンライン API を呼び出して JWS ステートメントの署名を検証できます。このプロセスは、GitHub の android-play-safetynet サンプル API の使用方法にも記載されています。オンラインの検証 API は初期段階のテスト専用であり、1 日あたりリクエスト 10,000 件という固定の割り当てがあります。

重要: オンラインの検証 API は、JWS メッセージが SafetyNet Attestation API のサーバーによって署名されたことの検証にのみ使用します。このオンライン API は、ペイロード内のフィールドがサービスの想定する値と一致するかどうかを確認できません。

予期しないケースに対する備え

変更および機能停止を考慮した使用方法をおすすめします。

API の変更
判定結果に新しい(試験運用版)フィールドが現れることは常にあり得ます。追加されたフィールドによりパーサーや使用ロジックが誤動作しないようにしてください。特に、SafetyNet API Clients メーリング リストで通知されていない試験運用版のフィールドに依存しないでください。
SafetyNet Attestation API のダウン

万が一、SafetyNet Attestation API が使用できない場合に備え、この API のユーザーはこの API とそのレスポンスの可用性品質への依存を動的に制御する機能を、サーバーサイドに構築することを強くおすすめします

一般的な戦略として、アプリがこの API を呼び出さないように動的に指示する機能の実装に加え、特定のデバイスやユーザーのクラスの SafetyNet Attestation API の結果を無視する、デバイスおよびユーザーの許可リストの作成があります。

サンプルコード

SafetyNet API の使用について詳しくは、GitHub にあるサンプルコードをご覧ください。

アナウンス用メーリング リスト

SafetyNet API Clients メーリング リストに参加して、SafetyNet Attestation API の最新情報を入手することを強くおすすめします。

フィードバック

この API についてのフィードバックをお寄せください。フィードバックはこの API の新機能に優先的に適用されます。

詳細

SafetyNet Attestation API を使用する際のおすすめの方法については、次のリンクをご覧ください。

追加利用規約

SafetyNet API にアクセス、またはこれを使用すると、Google API 利用規約と追加規約に同意したことになります。適用されるすべての利用規約とポリシーを確認し、理解したうえで API にアクセスしてください。

SafetyNet 利用規約

実際の計測から大量にデータを収集した場合は常に、偽陽性と偽陰性の両方の可能性があります。Google は把握できる限りのデータを提示しています。検出メカニズムを広範囲でテストして正確性を確保しており、引き続き正確性を維持するためにメソッドの改善に努めています。

適用される法律、規制、第三者の権利(データまたはソフトウェアの輸出入に関連する法律、プライバシーに関連する法律、現地の法律を含まれるが、これに限定されない)を遵守することに同意するものとします。違法行為あるいは第三者の権利の侵害を促進または助長するために 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 はできる限り透明性を確保してデベロッパーをサポートすることを心掛けていますが、デベロッパーに代わってユーザーに説明することはできません。アプリによるユーザーデータの収集、共有、アプリのセキュリティ対策に関して Google Play のデータ セーフティ セクションのフォームにどのように入力するかを決定する責任は、デベロッパーのみが負っています。