2013/06/03

Google Play の Licensing サービスを使う

Google Play には、そのアプリが正規に Google Play からダウンロードされ、そのアカウントに利用権限があるかどうか確認するためのサービスがあります。
これにより、 「有料アプリをダウンロード。その後、すぐ apk をSDカード等にコピーして、購入をキャンセル。すると無料でアプリを使用出来る」 という裏技が(簡単には)使用できなくなるので、有料アプリを公開している方は是非導入すべきでしょう。

ちなみに、LVL (License Verification Library) というと通りが良いようですが、LVL は Licensing Service を Android アプリに組み込む時に使用するライブラリのことです。

というわけで、Google Play の Licensing Service を導入してみました。

ライセンスキーの取得

以前はユーザ毎にライセンスキーが用意されていましたが、現在はアプリ毎にライセンスキーが必要です。
ちなみに、APKが無くても、アプリ情報だけを先に作成することで、ライセンスキーを取得することが出来ます。
2014/06/11 追記
現在はAPKが無いと取得できなくなったようです。
α版として登録し、一般公開しない状態で取得すれば良いとのこと。
Android デベロッパー ヘルプ

Developer Console → 全てのアプリケーション

アプリを選択 → サービスとAPI

「このアプリのライセンスキー」が表示されます。
LVL を導入した時に必要となりますので、控えておきましょう。

LVL のダウンロード

LVL は SDK Manager からダウンロードすることができます。
Windows ユーザの人は管理者権限での実行が必要かもしれません。


Google Play Licensing Library を選択し、 Install packages... を押します。

Android SDK 以下にダウンロードされたライブラリを自分のプロジェクトのディレクトリにコピーします。
Windows のデフォルトだと以下にあるはずです。インストールされるディレクトリはちょくちょく変わります。
C:\Program Files (x86)\Android\android-sdk\extras\google\play_licensing\library

sample や test というディレクトリもあると思いますが、導入には必要ありません。

LVL を Eclipse に取り込む

Eclipse を立ちあげて、
File → New → Project...

Android → Android Project from Existing Code

Root Directory に先ほどコピーしてきた library ディレクトリを指定します。

以下のように自分のアプリプロジェクトとLVLが表示されている状態になっていればOKです。

自分のアプリで LVL を使用可能にする

以後、自分のアプリプロジェクト名を SQLiteViewer と書きます。
適宜、自分のプロジェクトと読み替えて下さい。

まず、LVL をライブラリとして使用出来るようにします。
SQLiteViewer を右クリックし、
Property → Android → Library → Add → library (LVL) を指定
以下のように、正しく認識された場合は緑のチェックマークがつきます。

次に、SQLiteViewer の AndroidManifest.xml を開きます。
そこに、以下の一文を加えます。
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/>

この項目を加えても、Google Play の追加認証は必要ありません。

Policy の決定

Google Play licensing service を利用するには Policy というものを実装しなければなりません。
Policy は、licensing server とのやり取りをもとに、アプリの使用を許可するかどうかを最終的に決定するモジュールです。
LVL にはデフォルトで以下の2つの Policy が同梱されていますが、Policy クラスを継承して自分で作成する事も可能です。
基本的には ServerManagedPolicy を使用するのが良いでしょう。
  • ServerManagedPolicy
    server の提供する設定を利用し、結果をローカルキャッシュすることで様々なネットワーク状況にも対応する
  • StrictPolicy
    一切キャッシュを生成せず、毎回ネットワーク経由のライセンス認証にパスしなければ利用を許可しない
以後、ServerManagedPolicy を利用して説明を続けます自分で Policy を作成する場合は Guidelines for custom policies が参考になるでしょう。

実装

準備は整いましたので、あとは実装するだけです。
シンプルに書くと、以下のような感じです。

MainActivity.java
public class MainActivity extends Activity {
    //
    // 変更すべき点 1 
    //
    private static final String BASE64_PUBLIC_KEY = "ここをライセンスキーで書き換える";

    //
    // 変更すべき点2
    // ServerManagedPolicy を使用するために必要な種1
    // 20バイト のランダムな数字を指定する
    // 
    private static final byte[] SALT = new byte[] {
        -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95, -45, 77, -117, -36, -113, -11, 32, -64,
        89
    };

    private LicenseCheckerCallback mLicenseCheckerCallback;
    private LicenseChecker mChecker;

    // onCreate は難読化してもそのまま残るので、セキュリティ的にはイマイチ
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.licensing);

        //
        // 変更すべき点3
        // ServerManagedPolicy を使用するために必要な種2
        // デバイス固有の文字列を指定する
        // もっと複雑にしたほうが良い
        //
        String deviceId = Secure.getString(getContentResolver(), Secure.ANDROID_ID);

        // 認証完了時に呼ばれるコールバック
        mLicenseCheckerCallback = new MyLicenseCheckerCallback();

        // LicenseChecker の作成
        mChecker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY);

        // doCheck
        mChecker.checkAccess(mLicenseCheckerCallback);
    }

    private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
        @Override
        public void allow(int policyReason) {
            // 許可された場合
        }

        @Override
        public void dontAllow(int policyReason) {
            // 許可されなかった場合
            if (reason == Policy.RETRY) {
              // ネットワークエラーなどでうまく行かなかった場合
              // リトライするかどうか判断を仰ぐ
            } else {
              // 認証失敗
              // Google Play に行くかどうかのダイアログを表示する等
            }
        }

        @Override
        public void applicationError(int errorCode) {
            // デベロッパーのミスで起こるエラー
            // 実際のアプリでここが呼ばれることは無いようにしなければならない
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mChecker.onDestroy();
    }
}

基本的には LicenseChecker を作成し、LicenseCheckerCallback をセットして checkAccess を呼ぶという流れになります。

SALTdeviceIdServerManagedPolicy を使用するために必要な変数です。
ServerManagedPolicy は情報をローカルにキャッシュしますので、それを暗号化するための種となるわけです。StrictPolicy を使用する場合は必要ありません。

LicenseChecker.checkAccess() を呼ぶと、設定した Policy に基いて認証が行われ、 LicenseCheckerCallback.allow()LicenseCheckerCallback.dontAllow() が呼ばれます。
allow の場合は MainActivity 等に移動すれば良いですし、dontAllow の場合は policyReason を見てその後の動作を決めます。

applicationError() が発生したときは、実装にバグがあります。
大抵は BASE64_PUBLIC_KEY がサーバで表示されたものと異なっていたり、AndroidManifest.xml の package名と Developer Console で設定した package名が異なっていたりする可能性が高いです。

完成…ではない

以上で、Licensing サービスを使用する準備は整いました。
…と言いたいところなのですが、ここでちょっと注意が必要です。

勘の良い方は気づかれたかもしれませんが、この Licensing サービス、リバースエンジニアリングで簡単に破られてしまいます。
そのため、上記のようにわかりやすいコードをそのまま使うのは、セキュリティ的にあまり良くありません。
どこまで堅牢にするかはアプリの性質によるので一概に言えませんが、難読化や簡単な耐タンパ性の付加などはやったほうが良いかもしれません。

さて、次はデバッグにうつります。
穀風: Google Play の Licensing サービスをデバッグする

1 件のコメント:

Unknown さんのコメント...

はじめましてアンドロイドアプリ開発初心者で
ライセンス認証の方法を参考にさせて頂きました。