1. はじめに
最終更新日: 2023 年 1 月 4 日
Play Integrity とは
Play Integrity API を使用すると、リスクが潜んでいる可能性のある不正な操作からアプリとゲームを保護できます。Play Integrity API を使用してアプリとデバイスの完全性判定の結果を取得することによって、不正行為、偽装、不正アクセスなどの攻撃や不正使用のリスクを軽減する適切な措置を講じられます。
以前のソリューション
Play Integrity API はアプリのライセンス ライブラリと SafetyNet Attestation API という、これまでの 2 つのソリューションに代わるものです。新しいアプリの場合は Play Integrity が推奨されます。以前のソリューションを使用している既存のアプリの場合は、更新して Play Integrity を使用することをご検討ください。
パスを選択する
Play Integrity には、次のような異なる種類のアプリのライブラリ オプションが用意されています。
- C/C++ で記述されたゲームやその他のアプリ
- Kotlin または Java プログラミング言語で記述されたアプリ
- Unity エンジンで開発されたゲーム
この Codelab には、この 3 つのオプションすべてのパスが含まれています。開発のニーズに適したパスを選択してください。この Codelab では、バックエンド サーバーの構築とデプロイを行います。このサーバーは 3 つのアプリのタイプすべてで共通です。
作成するアプリの概要
この Codelab では、Play Integrity をサンプルアプリに統合し、API を使用してデバイスとアプリの完全性を確認します。小規模なバックエンド サーバー アプリをデプロイして、それを使って完全性チェック プロセスをサポートします。
学習内容
- Play Integrity ライブラリをアプリに統合する方法
- Play Integrity API を使用して完全性チェックを実施する方法
- サーバーを使用して完全性判定の結果を安全に処理する方法
- 完全性判定の結果の見方
この Codelab では Play Integrity に特化して説明します。関連性のない概念やコードブロックについては説明しませんが、そのままコピーして貼り付けられるようにしています。
必要なもの
- 有効な Android デベロッパー登録がされた、Google Play Console と Google Cloud Console にアクセスできる Google アカウント
- C++ パスまたは Kotlin パスの場合は Android Studio 2021.1.1 以降
- Unity パスの場合は Unity 2020 LTS 以降
- 開発者向けオプションと USB デバッグが有効になっている、パソコンに接続された Android 搭載デバイス
この Codelab には、ビルドの署名と Google Play Console へのアップロードに関するリソースのリンクが含まれていますが、このプロセスについてある程度知識を持っていることを前提となります。
2. コードを取得する
Codelab プロジェクトは Git リポジトリから入手できます。リポジトリの親ディレクトリには、次の 4 つのディレクトリがあります。
server
ディレクトリには、デプロイするサンプル サーバーのコードが含まれています。cpp
ディレクトリには、C++ のゲームやアプリに Play Integrity を追加するための Android Studio プロジェクトが含まれています。kotlin
ディレクトリには、標準の Android アプリに Play Integrity を追加するための Android Studio プロジェクトが含まれています。unity
ディレクトリには、Play Integrity を Unity プロジェクトに追加するための 2020 LTS バージョンの Unity エンジンで作成されたプロジェクトが含まれています。
各クライアント サンプル ディレクトリには、次の 2 つのサブディレクトリが含まれています。
start
ディレクトリには、この Codelab で修正するプロジェクトのバージョンが含まれています。final
ディレクトリには、Codelab の完了時のプロジェクトの状態と同じプロジェクトのバージョンが含まれています。
リポジトリのクローンを作成する
コマンドラインから、ルート add-play-integrity-codelab
ディレクトリを含めるディレクトリに変更し、次の構文を使用して GitHub からプロジェクトのクローンを作成します。
git clone https://github.com/android/add-play-integrity-codelab.git
依存関係を追加する(C++ のみ)
C++ パスを使用する場合は、リポジトリのサブモジュールを初期化して Dear ImGui ライブラリをセットアップする必要があります。これはユーザー インターフェースで使用されます。コマンドラインから次の手順で行います。
- 作業ディレクトリを
add-play-integrity-codelab/cpp/start/third-party
に変更します。 git submodule update --init
3. クライアントとサーバーの機能を理解する
Play Integrity セキュリティ モデルの重要な要素は、デバイスから検証オペレーションを自分が制御している安全なサーバーに移行することです。サーバーでこのオペレーションを行うことにより、不正使用されたデバイスによるリプレイ攻撃やメッセージの内容の改ざんなどの場合に保護できます。
Codelab サンプル サーバーは一例として作成されています。わかりやすくするために本番環境では備えておいた方がよい多くの機能は含まれていません。生成された値のリストはメモリにのみ保存され、永続ストレージによってバッキングされていません。サーバー エンドポイントは認証を必要とする設定にはなっていません。
performCommand エンドポイント
サーバーは HTTP POST
リクエストを介してアクセスされる /performCommand
エンドポイントを提供します。リクエスト本文は、次の Key-Value ペアを含む JSON ペイロードである必要があります。
{
"commandString": "command-here",
"tokenString": "token-here"
}
/performCommand
エンドポイントは次の Key-Value ペアを含む JSON ペイロードを返します。
{
"commandSuccess": true/false,
"diagnosticMessage": "summary text",
"expressToken": "token-here"
}
commandString
パラメータの実際のコンテンツは重要ではありません。サーバーは Play Integrity 使用時に commandString
の完全性を検証しますが、コマンド値は使用しません。すべてのクライアント バージョンで、値 "TRANSFER FROM alice TO bob CURRENCY gems QUANTITY 1000"
が使用されます。
tokenString
パラメータの値は次のいずれかである必要があります。
- Play Integrity API によって生成されたトークン
- 正常に行われた前回の
/performCommand
呼び出しで返されたエクスプレス トークン
サーバーは Play Integrity API から取得したトークンを復号して検証します。復号結果は完全性シグナルの JSON ペイロードになります。シグナルの値に応じて、サーバーはコマンドを承認するか、拒否するかを選択できます。トークンが正常に復号されると、diagnosticMessage
にシグナルの概要説明が返されます。通常、本番環境のアプリではこの情報をクライアントに返しませんが、Codelab クライアントではこれを使用して、サーバーログを確認することなく、オペレーションの結果を表示します。トークンの処理中にエラーが発生すると、diagnosticMessage
でエラーが返されます。
ノンスの生成
Play Integrity リクエストを実行するには、ノンスを生成してリクエストに関連付ける必要があります。ノンスを使用することによって、完全性リクエストが固有で、1 回のみ処理されるようにします。また、ノンスを使って、完全性リクエストに関連付けられたメッセージ コンテンツが改ざんされていないことも確認できます。Play Integrity のノンスについて詳しくは、こちらのドキュメントをご覧ください。
この Codelab では次の 2 つの値を組み合わせてノンスを生成します。
- 暗号で保護された乱数ジェネレータで生成した 128 ビットの乱数
commandString
値の SHA-256 ハッシュ
Play Integrity API では、ノンスは URL エンコードされたパディングされていない Base64 文字列であることが求められます。ノンス文字列を作成するために、この Codelab では乱数とハッシュ値のバイト配列を 16 進文字列に変換して連結します。生成された文字列は有効な Base64 文字列ですが、エンコードもデコードもされていません。
Codelab クライアントは、HTTP GET
リクエストを使って、サーバー上で /getRandom
エンドポイントを呼び出して、乱数を取得します。この方法は、コマンド リクエストで使用された乱数のソースであることをサーバーが確認できるため、最も安全なランダム生成方法です。ただし、この方法はサーバーへの追加のラウンドトリップが発生します。クライアントは乱数を自ら生成することで、セキュリティを天秤にかけたうえで、このラウンドトリップを回避できます。
エクスプレス トークン
PIA の呼び出しは高コストであるため、サーバーはエクスプレス トークンも提供しています。これは高セキュリティではないものの低コストで認証を可能にする代替手段です。エクスプレス トークンは完全性チェックに合格し /serverCommand
の呼び出しに成功すると expressToken
フィールドに返されます。Play Integrity API の呼び出しは計算コストが高く、価値の高いオペレーションの保護に使用することを想定しています。エクスプレス トークンを返すことにより、Play Integrity API で完全な検証を行うほど重要ではないオペレーションや頻度が多すぎるオペレーションを実行する際の低セキュリティの認証が可能になります。/serverCommand
を呼び出すときに Play Integrity トークンの代わりにエクスプレス トークンを使用できます。各エクスプレス トークンは 1 回しか使用できません。有効なエクスプレス トークンを使用した /serverCommand
の呼び出しが成功すると、新しいエクスプレス トークンが返されます。
Codelab の実装では、エクスプレス トークンはサーバーで一意に生成されるため、コマンドは引き続きリプレイ攻撃から保護されます。ただし、エクスプレス トークンではコマンド変更に対するハッシュ保護が省略されており、Play Integrity API の最初の呼び出し後に発生したデバイス変更を検出することができないため、安全性が低くなります。
Codelab サーバー アーキテクチャ
この Codelab では、Ktor フレームワークを使用して Kotlin で記述された付属のサンプル サーバー プログラムを使用して、サーバーをインスタンス化できます。このプロジェクトには、Google Cloud App Engine にデプロイするための構成ファイルが含まれています。この Codelab の手順では App Engine のビルドとデプロイについて説明します。別のクラウド プロバイダを使用している場合、Ktor は複数のクラウド サービスでのデプロイ手順を用意しています。必要に応じてプロジェクトを修正し、任意のサービスをデプロイしてください。独自のローカル サーバー インスタンスにデプロイすることもできます。
サーバーでのサンプルコードの使用は必須ではありません。使い慣れたウェブ アプリケーション フレームワークがある場合は、Codelab サンプル サーバーを参照しながら、独自のフレームワークに /getRandom
エンドポイントと /performCommand
エンドポイントを実装できます。
クライアントの設計
C++、Kotlin、Unity エンジンの 3 つのクライアント バージョンはすべて、ユーザー インターフェースが同じです。
[Request random] ボタンは、サーバーの /getRandom
エンドポイントを呼び出します。結果はテキスト フィールドに表示されます。これを使用して、Play Integrity 統合を追加する前に、サーバーの接続と動作を確認できます。
[Call server with integrity check] ボタンは、この Codelab の開始時は機能しません。次の手順でコードを追加します。
/getRandom
を呼び出し、乱数を取得- ノンスを生成
- ノンスを使用して Play Integrity リクエストを作成
- Play Integrity リクエストで生成されたトークンを使用して
/performCommand
を呼び出し /performCommand
の結果を表示
コマンドの結果はテキスト フィールドに表示されます。コマンドが正常に完了すると、こちらには Play Integrity チェックによって返されたデバイス判定結果の概要が表示されます。
[Call server with express token] ボタンは、[Call server with integrity check] オペレーションが正常に完了すると表示されます。先ほどの /performCommand
のエクスプレス トークンを使用して /performCommand
を呼び出します。テキスト フィールドにコマンドが正常に完了したか、失敗したかが表示されます。返されたエクスプレス トークンの値は、乱数に使用されたテキスト フィールドに表示されます。
Play Integrity API 関数はエラーコードを返して、エラーを報告します。このようなエラーコードの詳細については、Play Integrity のドキュメントをご覧ください。不安定なインターネット接続や過負荷のデバイスといった環境条件が原因で、エラーが発生することがあります。そのようなエラーが発生した場合は、指数バックオフによる再試行オプションの追加をご検討ください。この Codelab では、Play Integrity エラーは Logcat に出力されます。
4. Google Cloud App Engine をセットアップする
Google Cloud を使用するには、次の手順に沿って操作します。
- Google Cloud Platform に登録します。Google Play Console に登録している Google アカウントと同じアカウントを使用します。
- 請求先アカウントを作成します。
- Google Cloud SDK をインストールして初期化します。
新しくインストールした Google Cloud CLI を使用して、次のコマンドを実行します。
gcloud components install app-engine-java
で Java 用の App Engine 拡張機能をインストールします。gcloud projects create $yourprojectname --set-as-default
コマンドの一意の識別子を$yourname
に置き換えて、新しい Cloud プロジェクトを作成します。gcloud app create
で Cloud プロジェクトに App Engine アプリを作成します。
5. サーバーをデプロイする
アプリのパッケージ名を設定する
このステップでは、アプリのパッケージ名をサーバーコードに追加します。テキスト エディタで ValidateCommand.kt
ソースファイルを開きます。このファイルは次のディレクトリに保存されています。
add-play-integrity-codelab/server/src/main/kotlin/com/google/play/integrity/codelab/server/util
次の行を見つけ、プレースホルダ テキストを一意のパッケージ ID に置き換えて、ファイルを保存します。
const val APPLICATION_PACKAGE_IDENTIFIER = "com.your.app.package"
この ID は後ほど Google Play Console にアプリをアップロードする前に、クライアント プロジェクトに設定します。
サーバーをビルドして App Engine にデプロイする
Google Cloud CLI を使用して add-play-integrity/server
ディレクトリから次のコマンドを実行し、サーバーをビルドしてデプロイします。
Linux または macOS の場合:
./gradlew appengineDeploy
Microsoft Windows の場合:
gradlew.bat appengineDeploy
正常に完了したデプロイで出力されたデプロイ済みサービスの場所を書き留めます。この URL はサーバーと通信するためにクライアントを設定する際に必要となります。
デプロイを確認する
Google Cloud CLI を使用して次のコマンドを実行すると、サーバーが正常に動作していることを確認できます。
gcloud app browse
このコマンドを実行すると、ウェブブラウザとルート URL が開きます。サンプル サーバーにルート URL からアクセスすると、Hello World! というメッセージが表示されます。
6. Google Play Console でアプリを設定する
Google Play Console でアプリの完全性を設定する
Google Play Console に既存のアプリエントリがある場合は、この Codelab で使用できます。または、手順に沿って Google Play Console で新しいアプリを作成します。Google Play Console でアプリを選択または作成したうえで、アプリの完全性を設定する必要があります。Google Play Console の左側のメニューで、[リリース] セクションの [アプリの完全性] を選択します。
Google Cloud プロジェクトを作成してリンクする
[Cloud プロジェクトをリンク] ボタンをクリックします。サーバーで使用した Google Cloud プロジェクトを選択し、[プロジェクトをリンク] ボタンをクリックします。
Google Cloud によるサーバーへのアクセス
バックエンド サーバーでは、Play Integrity API によってクライアントで生成された完全性トークンを復号する必要があります。Play Integrity では 2 種類の鍵の管理方法から選べます。Google が生成し管理する鍵と、デベロッパーが提供する鍵です。この Codelab では、Google が管理する鍵の推奨されるデフォルト動作を使用します。
Google 管理の鍵を使用した場合、バックエンド サーバーは暗号化された完全性トークンを復号のために Google Play サーバーに渡します。Codelab サーバーは Google API クライアント ライブラリを使用して、Google Play サーバーと通信します。
これでサーバーが稼働を開始し、Google Play Console でのアプリの設定が完了しました。続いて、選択したプラットフォームに対応するクライアントをカスタマイズしていきます。特定のプラットフォーム向けのステップはすべてまとめて記載しているため、使用しないプラットフォームの手順はスキップしてください。
7. クライアントをビルドして実行する(C++)
Android Studio を実行します。[Welcome to Android Studio] ウィンドウで [Open] ボタンをクリックし、add-play-integrity-codelab/cpp/start
にある Android Studio プロジェクトを開きます。
アプリケーション ID を更新する
ビルドを Google Play にアップロードする前に、アプリケーション ID をデフォルトのものから一意のものに変更する必要があります。次の手順で行います。
- Android Studio の [Project] ペインで、
start/app
の下にあるbuild.gradle
ファイルを見つけて開きます。 applicationId
ステートメントを見つけます。com.google.play.integrity.codelab.cpp
をサーバーのデプロイ時に選んだパッケージ名に変更し、ファイルを保存します。- ファイルの上に、Gradle ファイルの変更を通知するバナーが表示されます。[Sync Now] をクリックして、ファイルを再読み込みし再同期します。
- Android Studio の [Project] ペインで、
start/app/src/main
の下にあるAndroidManifest.xml
ファイルを開きます。 package="com.example.google.codelab.playintegritycpp"
ステートメントを見つけます。com.example.google.codelab.playintegritycpp
を固有のパッケージ名に置き換えて、ファイルを保存します。- Android Studio の [Project] ペインで、
start/app/src/main/java/com.example.google.codelab.playintegritycpp
の下にあるPlayIntegrityCodelabActivity
ファイルを開きます。 package com.example.google.codelab.playintegritycpp
ステートメントを見つけます。com.example.google.codelab.playintegritycpp
を固有のパッケージ名に置き換えます。- 新しいパッケージ名を右クリックし、[Show Context Actions] を選択します。
- [Move to(新しいパッケージ名)] を選択します。
- 表示されている場合は、ファイルの上の [Sync Now] ボタンをクリックします。
サーバーの URL を更新する
プロジェクトを更新して、サーバーをデプロイした URL の場所を指すようにする必要があります。
- Android Studio の [Project] ペインで、
start/app/src/main/cpp
の下にあるserver_urls.hpp
ファイルを開きます。 - サーバーのデプロイ時に表示されたルート URL を
GET_RANDOM_URL
定義とPERFORM_COMMAND_URL
定義に追加し、ファイルを保存します。
結果は次のようになります。
constexpr char GET_RANDOM_URL[] = "https://your-play-integrity-server.uc.r.appspot.com/getRandom";
constexpr char PERFORM_COMMAND_URL[] = "https://your-play-integrity-server.uc.r.appspot.com/performCommand";
実際の URL はプロジェクト名と、サーバーのデプロイに使用した Google Cloud リージョンによって異なります。
ビルドして実行する
開発用に構成された Android デバイスを接続します。Android Studio でプロジェクトをビルドし、接続済みのデバイスで実行します。アプリには次のように表示されます。
[Request Random] ボタンをタップしてコードを実行し、サーバーに HTTP リクエストを送信して、乱数をリクエストします。しばらくすると、画面に乱数が表示されます。
エラー メッセージが表示された場合は、[Logcat] ペインの出力に詳細が記載されている場合があります。
正常に乱数値を取得でき、サーバーと通信できていることを確認したら、Play Integrity API の統合を開始します。
8. Play Integrity をプロジェクトに追加する(C++)
SDK をダウンロードする
Play Core SDK をダウンロードして展開する必要があります。次の手順を行います。
- Play Core Native SDK ページからから ZIP ファイルに入った Play Core SDK をダウンロードします。
- ZIP ファイルを展開します。
- 新しく展開したディレクトリの名前が
play-core-native-sdk
になっていることを確認し、そのディレクトリをadd-play-integrity-codelab/cpp/start
ディレクトリにコピーまたは移動します。
build.gradle を更新する
Android Studio の [Project] ペインで、start/app
ディレクトリ内のモジュール レベルの build.gradle
ファイルを開きます。
apply plugin: 'com.android.application'
行の下に次の行を追加します。
def playcoreDir = file("../play-core-native-sdk")
defaultConfig
ブロックで externalNativeBuild
ブロックを見つけ、cmake
ブロック内の arguments
ステートメントを次のように変更します。
arguments "-DANDROID_STL=c++_shared",
"-DPLAYCORE_LOCATION=$playcoreDir"
android
ブロックの末尾に次のコードを追加します。
buildTypes {
release {
proguardFiles getDefaultProguardFile("proguard-android.txt"),
"proguard-rules.pro",
"$playcoreDir/proguard/common.pgcfg",
"$playcoreDir/proguard/integrity.pgcfg"
}
}
dependencies
ブロックの末尾に次の行を追加します。
implementation files("$playcoreDir/playcore.aar")
変更内容を保存します。ファイルの上に、Gradle ファイルの変更を通知するバナーが表示されます。[Sync Now] をクリックして、ファイルを再読み込みし再同期します。
CMakeList.txt を更新する
Android Studio の [Project] ペインで、start/app/src/main/cpp
ディレクトリ内の CMakeLists.txt
ファイルを開きます。
find_package
コマンドの下に次の行を追加します。
include("${PLAYCORE_LOCATION}/playcore.cmake")
add_playcore_static_library()
target_include_directories(game PRIVATE
行を見つけ、その下に次の行を追加します。
${PLAYCORE_LOCATION}/include
target_link_libraries(game
行を見つけ、その下に次の行を追加します。
playcore
ファイルを保存します。ファイルの上に、外部ビルドファイルが変更されたことを通知するバナーが表示されます。[Sync Now] をクリックして、ファイルを再読み込みし再同期します。
[Build] メニューから [Make Project] を選択し、プロジェクトが正常にビルドされことを確認します。
9. 完全性リクエストを送信する(C++)
アプリは Play Integrity API を使用してトークンをリクエストし、完全性情報を取得します。その後このトークンをサーバーに送信して、復号と検証を行います。これからプロジェクトにコードを追加して Play Integrity API を初期化し、それを使用して完全性リクエストを送信します。
コマンドボタンを追加する
実際のアプリまたはゲームでは、ストアでの購入やマルチプレーヤー ゲーム セッションへの参加など、特定のアクティビティの前に完全性チェックを実施します。この Codelab ではボタンを UI に追加して、完全性チェックを手動でトリガーし、サーバーを呼び出して、生成された Play Integrity トークンを渡せるようにします。
Codelab プロジェクトには、client_manager.cpp
ソースファイルと client_manager.hpp
ソースファイルで定義された ClientManager
クラスが含まれています。便宜上、このファイルはすでにプロジェクトに追加されていますが、実装コードは含まれていないため、これから追加します。
UI ボタンを追加するには、Android Studio の [Project] ペインで、start/app/src/main/cpp
ディレクトリにある demo_scene.cpp
ファイルを開きます。まず、空の DemoScene::GenerateCommandIntegrity()
関数を見つけて、次のコードを追加します。
const auto commandResult =
NativeEngine::GetInstance()->GetClientManager()->GetOperationResult();
if (commandResult != ClientManager::SERVER_OPERATION_PENDING) {
if (ImGui::Button("Call server with integrity check")) {
DoCommandIntegrity();
}
}
次に、空の DemoScene::DoCommandIntegrity()
関数を見つけます。次のコードを追加します。
ClientManager *clientManager = NativeEngine::GetInstance()->GetClientManager();
clientManager->StartCommandIntegrity();
mServerRandom = clientManager->GetCurrentRandomString();
ファイルを保存します。次にサンプルの ClientManager
クラスを更新して、実際の Play Integrity 機能を追加します。
マネージャー ヘッダー ファイルを更新する
Android Studio の [Project] ペインで、start/app/src/main/cpp
ディレクトリにある client_manager.hpp
ファイルを開きます。
#include "util.hpp"
行の下に次の行を追加して、Play Integrity API のヘッダー ファイルを含めます。
#include "play/integrity.h"
ClientManager
クラスは IntegrityTokenRequest
オブジェクトと IntegrityTokenResponse
オブジェクトへの参照を保持する必要があります。ClientManager
クラス定義の一番下に次の行を追加します。
IntegrityTokenRequest *mTokenRequest;
IntegrityTokenResponse *mTokenResponse;
ファイルを保存します。
Play Integrity を初期化してシャットダウンする
Android Studio の [Project] ペインで、start/app/src/main/cpp
ディレクトリにある client_manager.cpp
ファイルを開きます。
ClientManager::ClientManager()
コンストラクタを見つけます。mInitialized = false;
ステートメントを次のコードに置き換えます。
mTokenRequest = nullptr;
mTokenResponse = nullptr;
const android_app *app = NativeEngine::GetInstance()->GetAndroidApp();
const IntegrityErrorCode errorCode = IntegrityManager_init(app->activity->vm,
app->activity->javaGameActivity);
if (errorCode == INTEGRITY_NO_ERROR) {
mInitialized = true;
} else {
mInitialized = false;
ALOGE("Play Integrity initialization failed with error: %d", errorCode);
ALOGE("Fatal Error: Play Integrity is unavailable and cannot be used.");
}
ClientManager::~ClientManager()
デストラクタに次のコードを追加します。
if (mInitialized) {
IntegrityManager_destroy();
mInitialized = false;
}
完全性トークンをリクエストする
Play Integrity API からの完全性トークンのリクエストは非同期処理です。トークン リクエスト オブジェクトを作成し、それにノンス値を割り当て、トークン リクエストを行う必要があります。そのために、空の ClientManager::StartCommandIntegrity()
関数に次のコードを追加します。
// Only one request can be in-flight at a time
if (mStatus != CLIENT_MANAGER_REQUEST_TOKEN) {
mResult = SERVER_OPERATION_PENDING;
// Request a fresh random
RequestRandom();
if (mValidRandom) {
GenerateNonce();
IntegrityTokenRequest_create(&mTokenRequest);
IntegrityTokenRequest_setNonce(mTokenRequest, mCurrentNonce.c_str());
const IntegrityErrorCode errorCode =
IntegrityManager_requestIntegrityToken(mTokenRequest, &mTokenResponse);
if (errorCode != INTEGRITY_NO_ERROR) {
// Log the error, in a real application, for potentially
// transient errors such as network connectivity, you should
// add retry with an exponential backoff
ALOGE("Play Integrity returned error: %d", errorCode);
CleanupRequest();
mStatus = CLIENT_MANAGER_IDLE;
} else {
mStatus = CLIENT_MANAGER_REQUEST_TOKEN;
}
}
}
トークン リクエストは非同期処理であるため、完了を確認する必要があります。ClientManager
クラスには、アプリ アップデート ループの一環で呼び出される Update()
関数があります。次のコードを ClientManager::Update()
関数に追加して、トークン リクエストのステータスを確認し、完了後に結果を処理します。
if (mStatus == CLIENT_MANAGER_REQUEST_TOKEN) {
IntegrityResponseStatus responseStatus = INTEGRITY_RESPONSE_UNKNOWN;
const IntegrityErrorCode errorCode =
IntegrityTokenResponse_getStatus(mTokenResponse, &responseStatus);
if (errorCode != INTEGRITY_NO_ERROR) {
// Log the error, in a real application, for potentially
// transient errors such as network connectivity, you should
// add retry with an exponential backoff
ALOGE("Play Integrity returned error: %d", errorCode);
CleanupRequest();
mStatus = CLIENT_MANAGER_IDLE;
} else if (responseStatus == INTEGRITY_RESPONSE_COMPLETED) {
std::string tokenString = IntegrityTokenResponse_getToken(mTokenResponse);
SendCommandToServer(tokenString);
CleanupRequest();
mStatus = CLIENT_MANAGER_RESPONSE_AVAILABLE;
}
}
リクエスト オブジェクトをクリーンアップする
トークン リクエスト オブジェクトとレスポンス オブジェクトの処理が完了したら、Play Integrity API に通知して、それらを破棄しリソースを再利用できるようにする必要があります。次のコードを ClientManager::CleanupRequest()
関数に追加します。
if (mTokenResponse != nullptr) {
IntegrityTokenResponse_destroy(mTokenResponse);
mTokenResponse = nullptr;
}
if (mTokenRequest != nullptr) {
IntegrityTokenRequest_destroy(mTokenRequest);
mTokenRequest = nullptr;
}
[Build] メニューから [Make Project] を選択し、プロジェクトが正常にビルドされることを確認します。
10. トークンをサーバーに送信する(C++)
次に完全性トークンを含むサーバーにコマンドを送信するコードを追加します。また、結果を処理するコードも追加します。
次のコードを ClientManager::SendCommandToServer()
関数に追加します。
// Note that for simplicity, we are doing HTTP operations as
// synchronous blocking instead of managing them from a
// separate network thread
HTTPClient client;
std::string errorString;
// Manually construct the json payload for ServerCommand
std::string payloadString = COMMAND_JSON_PREFIX;
payloadString += TEST_COMMAND;
payloadString += COMMAND_JSON_TOKEN;
payloadString += token;
payloadString += COMMAND_JSON_SUFFIX;
auto result = client.Post(PERFORM_COMMAND_URL, payloadString, &errorString);
if (!result) {
ALOGE("SendCommandToServer Curl reported error: %s", errorString.c_str());
mResult = SERVER_OPERATION_NETWORK_ERROR;
} else {
ALOGI("SendCommandToServer result: %s", (*result).c_str())
// Preset to success, ParseResult will set a failure result if the parsing
// errors.
mResult = SERVER_OPERATION_SUCCESS;
ParseResult(*result);
}
次のコードを ClientManager::ParseResult()
関数に追加します。
bool validJson = false;
JsonLookup jsonLookup;
if (jsonLookup.ParseJson(resultJson)) {
// Look for all of our needed fields in the returned json
auto commandSuccess = jsonLookup.GetBoolValueForKey(COMMANDSUCCESS_KEY);
if (commandSuccess) {
auto diagnosticString = jsonLookup.GetStringValueForKey(DIAGNOSTICMESSAGE_KEY);
if (diagnosticString) {
auto expressString = jsonLookup.GetStringValueForKey(EXPRESSTOKEN_KEY);
if (expressString) {
if (*commandSuccess) {
// Express token only valid if the server reports the command succeeded
mValidExpressToken = true;
} else {
mValidExpressToken = false;
mResult = SERVER_OPERATION_REJECTED_VERDICT;
}
mCurrentSummary = *diagnosticString;
mCurrentExpressToken = *expressString;
validJson = true;
}
}
}
}
if (!validJson) {
mResult = SERVER_OPERATION_INVALID_RESULT;
}
次に署名済みの App Bundle を生成して Google Play Console にアップロードし、アプリをテストします。
11. ビルドしてアップロードする(C++)
アプリのキーストアを作成してセットアップする
Android では、すべてのアプリはデバイスにインストールまたは更新される前に証明書でデジタル署名されている必要があります。
この Codelab では、アプリのキーストアを作成します。既存のゲームのアップデートを公開する場合は、アプリの以前のバージョンをリリースしたときと同じキーストアを再度使用します。
キーストアを作成してリリース App Bundle をビルドする
Android Studio でのキーストアの手順に沿ってキーストアを作成し、それを使用してゲームの署名付きリリースビルドを生成します。Android Studio で [Build] メニューから [Generate Signed Bundle / APK] を選択し、ビルドプロセスを開始します。[Android App Bundle] か [APK] を選択するよう求めるメッセージが表示されたら、[App Bundle] オプションを選択します。このプロセスが完了すると、Google Play Console へのアップロードに適した .aab ファイルが作成されます。
Google Play Console にアップロードする
App Bundle ファイルを作成したら、Google Play Console にアップロードします。内部テストトラックを使用して、ビルドにすばやくアクセスできるようにすることをおすすめします。
テストビルドを実行する
テストビルドを Google Play ストアからダウンロードして実行します。このケースでは、関数にプレースホルダの成功コードが含まれており、サーバーに完全性トークンを送信するため、完全性チェックは正常に開始し、次のような結果が画面に表示されます。
これで Play Integrity を C++ アプリに統合できました。続けてその他のクライアントの例を確認するか、この Codelab の最後までスキップしてください。
12. クライアントをビルドして実行する(Unity)
Codelab の Unity プロジェクトは Unity 2020 LTS(2020.3.31f1)を使用して作成されていますが、上位バージョンの Unity とも互換性があります。Unity 用 Play Integrity プラグインは、Unity 2018 LTS 以降と互換性があります。
プロジェクトのセットアップ
次の手順で行います。
- Unity Hub または Unity エディタで、
add-play-integrity-codelab/unity/start
にある Unity プロジェクトを開きます。 - プロジェクトが読み込まれたら、Unity の [File] メニューから [Build Settings...] を選択します。
- [Build Settings] ウィンドウで、プラットフォームを [Android] に変更します。
- [Build Settings] ウィンドウで、[Player Settings...] ボタンをクリックします。
- [Project Settings] ウィンドウで、[Player] カテゴリを選択し、[Settings for Android] セクションを見つけます。[Other Settings] リストを開きます。
- [Identification] の下にある [Package Name] を見つけます。
- パッケージ名をサーバーのデプロイ時に選択した ID に変更します。
- [Project Settings] ウィンドウを閉じます。
- Unity の [File] メニューから [Save Project] をクリックします。
サーバー URL を更新する
プロジェクトを更新して、サーバーをデプロイした URL の場所を指すようにする必要があります。手順は次のとおりです。
- IDE またはテキスト エディタで、
Scripts
フォルダのPlayIntegrityController.cs
ファイルを開きます。 URL_GETRANDOM
変数とURL_PERFORMCOMMAND
変数の値を変更して、サーバーを指定するようにします。- ファイルを保存します。
結果は次のようになります。
private readonly string URL_GETRANDOM = "https://your-play-integrity-server.uc.r.appspot.com/getRandom";
private readonly string URL_PERFORMCOMMAND = "https://your-play-integrity-server.uc.r.appspot.com/performCommand";
実際の URL はプロジェクト名と、サーバーのデプロイに使用した Google Cloud リージョンによって異なります。
サーバー機能をテストする
サーバー機能をテストするには、Unity エディタでプロジェクトを実行します。次の手順で行います。
Scenes
フォルダにあるSampleScene
シーンファイルを開きます。- エディタで [Play] ボタンをクリックします。
- [Game] ディスプレイの [Request Random] ボタンをクリックします。
しばらくすると、次のように乱数が画面に表示されます。
13. Play Integrity プラグインをプロジェクトに追加する(Unity)
プラグインをダウンロードする
ウェブブラウザで、Play Unity Plugin GitHub リポジトリのリリースページを開きます。プラグインの最新リリースを使用します。com.google.play.integrity-<version>
をダウンロードします。[Assets] リストで、Play Integrity の unitypackage
ファイルを選択します。
プラグインをインストールする
Unity エディタのメニューバーで、[Assets] -> [Import Package] -> [Custom Package...] の順にクリックし、ダウンロードした .unitypackage
ファイルを開きます。[Import Unity Package] ウィンドウが表示されたら、[Import] ボタンをクリックします。
このプラグインには、Unity 用 External Dependency Manager(EDM4U)が含まれています。EDM4U は Play Integrity の使用で必要な Java コンポーネントの依存関係解決の自動化を実装します。依存関係解決の自動化を有効にするように求められたら、[Enable] ボタンをクリックします。
解決の自動化を使用することを強くおすすめします。依存関係の問題はプロジェクトのビルドまたは実行の失敗につながる可能性があります。
14. 完全性リクエストを送信する(Unity)
完全性リクエストを作成する
完全性リクエストを作成するには、次の手順に沿って行います。
- IDE またはテキスト エディタで、
Scripts
フォルダのPlayIntegrityController.cs
ファイルを開きます。 - ファイルの一番上にある
using
ステートメントのブロックに次の行を追加します。
using Google.Play.Integrity;
RunIntegrityCommand()
関数を見つけ、yield return null;
ステートメントを次のコードに置き換えます。
// Call our server to retrieve a random number.
yield return GetRandomRequest();
if (!string.IsNullOrEmpty(_randomString))
{
// Create an instance of an integrity manager.
var integrityManager = new IntegrityManager();
// Request the integrity token by providing a nonce.
var tokenRequest = new IntegrityTokenRequest(GenerateNonceString(_randomString,
TEST_COMMAND));
var requestIntegrityTokenOperation =
integrityManager.RequestIntegrityToken(tokenRequest);
// Wait for PlayAsyncOperation to complete.
yield return requestIntegrityTokenOperation;
// Check the resulting error code.
if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError)
{
// Log the error, in a real application, for potentially
// transient errors such as network connectivity, you should
// add retry with an exponential backoff
Debug.Log($@"IntegrityAsyncOperation failed with error:
{requestIntegrityTokenOperation.Error.ToString()}");
yield break;
}
// Get the response.
var tokenResponse = requestIntegrityTokenOperation.GetResult();
// Send the command to our server with a POST request, including the
// token, which will be decrypted and verified on the server.
yield return PostServerCommand(tokenResponse.Token);
}
コマンドをサーバーに送信する
引き続き PlayIntegrityController.cs
ファイルを編集します。PostServerCommand()
関数を見つけて、yield return null;
ステートメントを次のコードに置き換えます。
// Start a HTTP POST request to the performCommand URL, sending it the
// command and integrity token data provided by Play Integrity.
var serverCommand = new ServerCommand(TEST_COMMAND, tokenResponse);
var commandRequest = new UnityWebRequest(URL_PERFORMCOMMAND, "POST");
string commandJson = JsonUtility.ToJson(serverCommand);
byte[] jsonBuffer = Encoding.UTF8.GetBytes(commandJson);
commandRequest.uploadHandler = new UploadHandlerRaw(jsonBuffer);
commandRequest.downloadHandler = new DownloadHandlerBuffer();
commandRequest.SetRequestHeader(CONTENT_TYPE, JSON_CONTENT);
yield return commandRequest.SendWebRequest();
if (commandRequest.result == UnityWebRequest.Result.Success)
{
// Parse the command result Json
var commandResult = JsonUtility.FromJson<CommandResult>(
commandRequest.downloadHandler.text);
if (commandResult != null)
{
resultLabel.text = commandResult.diagnosticMessage;
_expressToken = commandResult.expressToken;
if (commandResult.commandSuccess)
{
resultLabel.color = Color.green;
expressButton.SetActive(true);
}
else
{
resultLabel.color = Color.black;
expressButton.SetActive(false);
}
}
else
{
Debug.Log("Invalid CommandResult json");
}
}
else
{
Debug.Log($"Web request error on processToken: {commandRequest.error}");
}
ファイルを保存します。
15. ビルドしてアップロードする(Unity)
Unity エディタの Android Keystore Manager を使用し、Google Play Console にアップロードできるようビルドに署名を設定します。
署名情報の設定後に、次の手順を行います。
- Unity の [File] メニューから [Build] -> [Build Settings...] を選択します。
SampleScene
が [Scenes in Build] リストに含まれていることを確認します。- [Build App Bundle (Google Play)] チェックボックスがオンになっていることを確認します。
- [Build] ボタンをクリックし、エクスポート ファイルに名前を付けます。
App Bundle ファイルを作成したら、Google Play Console にアップロードします。内部テストトラックを使用して、ビルドにすばやくアクセスできるようにすることをおすすめします。
これで、ビルドをダウンロードしてインストールし、完全性チェックを実行できるようになりました。結果は次のようになります。
これで Play Integrity を Unity エンジン プロジェクトに統合できました。続けてその他のクライアントの例を確認するか、この Codelab の最後までスキップしてください。
16. プロジェクトをビルドして実行する(Kotlin)
Android Studio を実行します。[Welcome to Android Studio] ウィンドウで [Open] ボタンをクリックし、add-play-integrity-codelab/kotlin/start
にある Android Studio プロジェクトを開きます。
アプリケーション ID を更新する
ビルドを Google Play にアップロードする前に、アプリケーション ID をデフォルトのものから一意のものに変更する必要があります。次の手順で行います。
- Android Studio の [Project] ペインで、モジュール
PlayIntegrityCodelab.app
のbuild.gradle
ファイルを開きます。 applicationId
ステートメントを見つけます。com.example.google.codelab.playintegritykotlin
をサーバーのデプロイ時に選択した ID に変更し、ファイルを保存します。- ファイルの上に、Gradle ファイルの変更を通知するバナーが表示されます。[Sync Now] をクリックして、ファイルを再読み込みし再同期します。
サーバーの URL を更新する
プロジェクトを更新して、サーバーをデプロイした URL の場所を指すようにする必要があります。手順は次のとおりです。
- Android Studio の [Project] ペインで、
start/app/src/main/java/com.example.google.codelab.playintegritykotlin/integrity
の下にあるIntegrityServer
ファイルを開きます。 - URL を
‘https://your.play-integrity.server.com'
からサーバーのベース URL に変更し、ファイルを保存します。
結果は次のようになります。
private val SERVER_URL: String = "https://your-play-integrity-server.uc.r.appspot.com"
実際の URL はプロジェクト名と、サーバーのデプロイに使用した Google Cloud リージョンによって異なります。
ビルドして実行する
開発用に構成された Android デバイスを接続します。Android Studio でプロジェクトをビルドし、接続済みのデバイスで実行します。アプリには次のように表示されます。
起動時に、アプリはサーバー上で getRandom
エンドポイントを呼び出し、結果を表示します。URL が正しくない、サーバーが機能していないなどのエラーが発生した場合は、エラー ダイアログが表示されます。[Request Random] ボタンをクリックすると、サーバーから新しい乱数を取得できます。[Call server with integrity check] ボタンはまだ機能していません。この機能は次のセクションで追加します。
17. Play Integrity をプロジェクトに追加する(Kotlin)
Play Integrity ライブラリとサポートする依存関係をプロジェクトに追加する手順は次のとおりです。
- Android Studio の [Project] ペインで、
start/app
の下にあるbuild.gradle
ファイルを開きます。 - ファイルの下にある
dependencies
ブロックを見つけます。 dependencies
ブロックの末尾に次の行を追加します。
implementation "com.google.android.play:integrity:1.0.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.1"
- ファイルを保存します。
- ファイルの上に、Gradle ファイルの変更を通知するバナーが表示されます。[Sync Now] をクリックして、ファイルを再読み込みし再同期します。
Kotlin のサンプルではコルーチンを使用します。kotlinx-coroutines-play-services
ライブラリによる拡張機能で、Kotlin コルーチン内から Play Integrity 非同期 Task
オブジェクトを簡単に操作できます。
18. 完全性リクエストを送信する(Kotlin)
Android Studio の [Project] ペインで、start/app/src/main/java/com.example.google.codelab.playintegritykotlin/integrity
の下にある IntegrityServer
ファイルを開きます。ファイルの下には空の integrityCommand
関数があります。[Call server with integrity check] ボタンをクリックすると、UI はこの関数を呼び出します。
integrityCommand
関数にコードを追加して、次の操作を行います。
- ノンスの作成に使用する新しい乱数をサーバーから取得して、完全性チェックに関連付けます。
- Play Integrity API を呼び出して完全性リクエストを送信し、結果を含む完全性トークンを受け取ります。
- HTTP
POST
リクエストを使用して、コマンドと完全性トークンをサーバーに送信します。 - 結果を処理して表示します。
空の integrityCommand
関数に次のコードを追加します。
// Set our state to working to trigger a switch to the waiting UI
_serverState.emit(ServerState(
ServerStatus.SERVER_STATUS_WORKING))
// Request a fresh random from the server as part
// of the nonce we will generate for the request
var integrityRandom = IntegrityRandom("", 0U)
try {
val returnedRandom = httpClient.get<IntegrityRandom>(
SERVER_URL + "/getRandom")
integrityRandom = returnedRandom
} catch (t: Throwable) {
Log.d(TAG, "getRandom exception " + t.message)
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_UNREACHABLE,
IntegrityRandom("", 0U)))
}
// If we have a random, we are ready to request an integrity token
if (!integrityRandom.random.isNullOrEmpty()) {
val nonceString = GenerateNonce.GenerateNonceString(TEST_COMMAND,
integrityRandom.random)
// Create an instance of an IntegrityManager
val integrityManager = IntegrityManagerFactory.create(context)
// Use the nonce to configure a request for an integrity token
try {
val integrityTokenResponse: Task<IntegrityTokenResponse> =
integrityManager.requestIntegrityToken(
IntegrityTokenRequest.builder()
.setNonce(nonceString)
.build()
)
// Wait for the integrity token to be generated
integrityTokenResponse.await()
if (integrityTokenResponse.isSuccessful && integrityTokenResponse.result != null) {
// Post the received token to our server
postCommand(integrityTokenResponse.result!!.token(), integrityRandom)
} else {
Log.d(TAG, "requestIntegrityToken failed: " +
integrityTokenResponse.result.toString())
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_FAILED_TO_GET_TOKEN))
}
} catch (t: Throwable) {
Log.d(TAG, "requestIntegrityToken exception " + t.message)
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_FAILED_TO_GET_TOKEN))
}
}
サーバーへのコマンドを POST
するコードは、別々の postCommand
関数に分割されます。空の postCommand
関数に次のコードを追加します。
try {
val commandResult = httpClient.post<CommandResult>(
SERVER_URL + "/performCommand") {
contentType(ContentType.Application.Json)
body = ServerCommand(TEST_COMMAND, tokenString)
}
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_REACHABLE,
integrityRandom,
commandResult.diagnosticMessage,
commandResult.commandSuccess,
commandResult.expressToken))
} catch (t: Throwable) {
Log.d(TAG, "performCommand exception " + t.message)
_serverState.emit(ServerState(ServerStatus.SERVER_STATUS_UNREACHABLE))
}
不足しているインポートを解決して、ファイルを保存します。
19. 結果を表示する(Kotlin)
判定結果の概要を使って UI を更新する
現在、UI には完全性判定の結果に関する概要のプレースホルダ テキストが表示されています。プレースホルダを実際の概要に置き換えるには、次の手順を行います。
- Android Studio の [Project] ペインで、
start/app/src/main/java/com.example.google.codelab.playintegritykotlin/ui/main
の下にあるMainView.kt
ファイルを開きます。 - MainUI 関数の末尾に移動して、
text = "None",
ステートメントを見つけ、次のコードに置き換えます。
text = state.serverState.serverVerdict,
- 不足しているインポートを解決して、ファイルを保存します。
20. ビルドしてアップロードする(Kotlin)
アプリのキーストアを作成してセットアップする
Android では、すべてのアプリはデバイスにインストールまたは更新される前に証明書でデジタル署名されている必要があります。
この Codelab では、アプリのキーストアを作成します。既存のゲームのアップデートを公開する場合は、アプリの以前のバージョンをリリースしたときと同じキーストアを再度使用します。
キーストアを作成してリリース App Bundle をビルドする
Android Studio でのキーストアの手順に沿ってキーストアを作成し、それを使用してゲームの署名付きリリースビルドを生成します。Android Studio で [Build] メニューから [Generate Signed Bundle / APK] を選択し、ビルドプロセスを開始します。[Android App Bundle] か [APK] を選択するよう求めるメッセージが表示されたら、[App Bundle] オプションを選択します。このプロセスが完了すると、Google Play Console へのアップロードに適した .aab ファイルが作成されます。
Google Play Console にアップロードする
App Bundle ファイルを作成したら、Google Play Console にアップロードします。内部テストトラックを使用して、ビルドにすばやくアクセスできるようにすることをおすすめします。
テストビルドを実行する
テストビルドを Google Play ストアからダウンロードして実行します。[Call server with integrity check] ボタンが正常にクリックできる状態になっており、次の画面が表示されます。
21. 完了
これで Android アプリに Play Integrity API を正常に追加できました。