2016年07月29日

[開発者向け] Android 有料アプリでライセンス認証(LVL)まとめ

 さて、来月からは iOS 版の開発作業に入りますので、Android のノウハウで書き残しておきたい事は忘れないうちに書いておきましょう。

 まずは、Google Play で有料アプリを配布する際には一応組み込んでおきたい、ライセンス認証ライブラリ、通称 LVL (License Verification Library) のこと。
 Android は、悪い人がちょっとヤル気を出せば、有料アプリを購入 > 端末からアプリだけ抜き出す > 即座に購入キャンセルして返金、という事ができてしまいます。なので、たとえアプリが抜き出されたとしても、購入者以外はマトモに起動できないように、アプリ起動時に Google Play に問い合わせるためのライブラリですね。

 といっても、この LVL 入れたところで、もっとヤル気のある悪い人がアプリを解析して認証処理を無効化されたらそれまでだし、何より気になるのが、LVL に関する公式情報やブログ、挙句は 「LVL を誰でも簡単に無効化できてしまう某有名ハッキングツール」 さえも、数年前の日付のまま誰も更新してない ところを見ると、とっくに時代遅れで、誰もアテにしてないんでしょうね。今時はアプリ本体は無料配布で、サーバから提供されるデータやサービスに対してお金を払う商売が主流なので、そもそもアプリ本体にコピー対策をする必要もないのでしょう。

 でもそういう商売をするにはサーバとユーザー情報を管理をし続けないといけない訳で、個人で極力そんな事したくないし、LVL だけでも何もしないよりは遥かにマシなので、今更ながら大マジメに LVL を組み込んでみたので、その覚え書きです。
 なお、今更こんなとこでまとめなくても、世の中にはもっと丁寧にまとめてくださってる方がたくさん居られるので、ここでは他であんま書かれてない Android Stuido のプロジェクトへの組み込みや、2016年現在のテスト事情などを書きます。

LVL を組み込む上でとても役に立つサイト
 今回組み込む際に非常に参考になったサイトを先に紹介しておきます。あと、LVLは組み込む事自体は簡単なのですが、サンプル通りに組み込んだだけではアッサリ無効化されるので、じゃあどうすればいいのか、というのがとてもよくまとめられてるサイト。
Android Studio プロジェクトに LVL の組み込み
  1. SDK Manager > SDK Tools から、Google Play Licensing Library を取得。
    Android SDK 内の以下にダウンロードされるが、これだけでアプリソースから import できる訳じゃないし、フォルダ構成が Eclipse 向けなので、そのままでは使えない。
    {AndroidSDK}/extras/google/market_licensing
      +- library : 認証するためのコードをライブラリ化したもの
      +- sample  : ごく単純な組み込み例(※この通り組むと速攻で破られます)
      +- test    : 検証用コード(組み込み側には不要)
    
  2. File > New > Import Module で、上記の library ディレクトリをインポート。モジュール名は何でもいいけど、ここでは lvl とでもしときます。インポートオプションはデフォルトで問題なさげ。
    なおインポートの際、library に含まれてる aidl ファイルはスキップされて、自分でやれ的な事を言われますが、普通に使う分にはなくても問題ありません。これが何なのかは 後述
  3. アプリソースから import できるように、app 側の build.gradle の dependencies にインポートしたモジュール (ここではlvl) を追加。
    app/build.gradle
    dependencies {
        ...
        compile project(':lvl')
    }
    
  4. app のマニフェストにライセンス認証をするための権限を追加。
    AndroidManifest.xml
    <uses-permission android:name="com.android.vending.CHECK_LICENSE"/>
    
  5. ライブラリのソースが古すぎて、Lollipop (API 21) 以上だとクラッシュするので、以下のサイトを参考に、LicenseChecker.java を書き換え。 要点だけメモしておくと、bindService() にインテントを渡す前に、パッケージ名を明示的に指示しないといけない。なお、LVL のデフォルトのビルド設定は minSdkVersion=3 になってて、setPackage() は使えないので、minSdkVersion=4 以上に変更。
    LicenseChecker.java
    Intent intent = new Intent( ... );
    intent.setPackage( "com.android.vending" );
    boolean bindResult = mContext.bindService( intent, ... );
    
  6. ごく簡単な認証処理のサンプルを、メインの Activity にでも組み込んでみる。
    ライブラリに含まれてるサンプルでもいいし、前述したこちらのサイト のが一番シンプルかな。
    といっても、まずは先に Google Play に Developer 登録してないと、組み込みに必要な認証キーが取得できないんですけど。
 ということで、これでとりあえずアプリへの組み込みは完了。でも認証処理のテストをするには、これから長い長い道のりが待ってるのですが、その辺は次で。
LVL のテストをするまでの長い道のり
 アプリには組み込んだものの、そのアプリが購入済みかどうか調べて回答してくれるのは Google Play のサーバな訳で、そのためにアプリの情報をサーバ側に登録しないといけません。そのために必要な事を順に並べていきます。
  1. $25 払って、Google Play のデベロッパーになる。
  2. Developer Console で、とりあえず新規アプリを追加。公開やAPKのアップは不要。
 まずここまでで、アプリに LVL を組み込む際に必要な、BASE64_PUBLIC_KEY がもらえるようになります。Developer Console のアプリごとの設定にある サービスとAPI > このアプリのライセンス キー というやつですね。
 でも、このキーをアプリに組み込んで実際に起動してみても、サーバは 「ERROR_NOT_MARKET_MANAGED (そんなアプリ登録されてねえよ)」 しか返しません。サーバに値を返してもらうためには、さらに以下が必要。こっからがめんどい。
  1. 正式なアプリIDをつけて、正式な署名もした release 版の APK を用意し、Developer Console にアップロード。
  2. アプリを公開するために最低限必要な画像や説明文などを全て揃えて、アルファ版としてクローズド公開する。この際、100円でもいいので必ず値段をつけて、有料アプリとして公開する。
  3. Developer Console の全体設定の アカウントの詳細 > テスト用のアクセス権がある Gmail アカウント に、テストに使う端末のメインアカウントを記入。
    20160729a.png
 ここまでしてやっと、上の画像の ライセンス テスト応答 の値を変えることで、サーバがその値を返してくれるようになります。公開しないとテストすらできないのは敷居が高すぎだよ。
 なお、アルファ版だろうが、一度でも公開ボタンを押してしまったアプリはもう二度とGoogle Play から消せません ので、基本誰にも見られないとはいえ、それなりの覚悟で押しましょう。

 で、ですね。これでやっと認証処理のテストができるのですが、失敗系のテストはともかく、認証成功のテストをするためにライセンステスト応答を 「LICENSED」 にした場合、確かに成功コードは返るのですが、それに 必ず付随するはずの情報、VT, GT, GR (詳しくは後述)が付いてないので、実際の購入時と同じ挙動にはなりません。つーかぶっちゃけ何のテストにもなりません。
 じゃあ実際の製品と同じ挙動を確認するにはどうするかというと、実際に自腹でアプリを購入するしかないみたい。いやマジで!。いくら調べてもそれ以外の方法が見つからん。

 といっても、テスト用のアクセス権がある Gmail アカウント に登録されてるアカウントなら実際には課金されませんし (※よーく確認しないと本課金されるので注意)、知人に無料配布するためのプロモーションコードを使うという手もあるので、本当に自腹を切る必要はないのですが、そもそもダミーでもマトモな回答を返しておくれよ。
LVL をテストする際の注意点
ライセンス テスト応答 「RESPOND_NORMALLY」 について
 ダミーの返答ではなく、本来返すべき値を返します。なので、アプリを実際に購入しない限りは 「NOT_LICENSED」 ですし、実際に購入済みであれば 「LICENSED」 が返ります。
アプリの versionCode を上げると認証失敗する
 アプリの新バージョンを公開するために versionCode を上げると、サーバ的には未知の APK なので、認証失敗します(ERROR_NOT_MARKET_MANAGED)。つまり、最低限アルファ公開しないと起動チェックもできません。理屈は分かるんだけど、あまりにウザイ。しかも公開するたび30分くらい待たされるし。
 さらにウザいのが、一度 APK を公開してしまうと、もう同じ versionCode で APK はアップできないので、修正のたび versionCode を上げないといけない。ものすごく不快。
エミュレータでの認証処理テスト
 Google Play の認証サーバには、Google Play 接続サービスが利用できる端末でないとアクセスできません。なので、実機ならまず問題ないですが、エミュレータだと物によっては接続できません。Android 6.0 のエミュレータは Google アカウント設定をすれば接続できましたが、4.4 のエミュレータではダメでした(接続サービスにバインドできない)。
「テスト用のアクセス権がある Gmail アカウント」を複数指定
 複数アカウントを登録したい場合、以下のようにカンマ区切りで指定できます。
account1@gmail.com, account2@gmail.com, account3@gmail.com
 ただ、あの入力欄の挙動はどうにも怪しくて、普段はすぐ反映されるのですが、いつまでも反映されない事があります。そんな時は、入力欄を一度全部消して「保存」し、また同じ内容を書き込んで「保存」 しなおせば反映されます。勘弁してよ。
あまりしつこく認証サーバにアクセスすると拒否される
 認証処理のテストのためにあまり何度も認証サーバにアクセスすると、サーバからアクセス制限されて ERROR_CONTACTING_SERVER しか返さなくなります。
 この状況になると数時間はアクセスできなくなるのですが、困るのが、端末がオフラインの場合と同じエラー なので、ユーザーへの説明が 「通信できないためアプリの認証ができません。もし通信可能な状況でしたら、しばらく待って再度お試しください」 みたいなしつこい文章になります。せめて別のエラーコードにしてくれよ。
実際に購入済みの製品で認証サーバが返す値
 実際に購入済みのアプリで認証処理をした場合、成功コードのほかに、次回いつ認証するのが適切か判断するための、次の情報が返ります。
  • VT : 次にサーバに認証すべき日時
  • GT : 次回認証時にオフラインだとしても、使用を許容する期限
  • GR : 次回認証時にオフラインだとしても、使用を許容する起動回数
 実際にアプリをテスト購入して、上記にどんな値が返るのか試してみたところ、2016/07 現在では、VT=1日後、GT=1週間後、GR=10 を返すようです。
 LVL のデフォルトの ServerManagedPolicy の実装では、オフラインの場合は、GT か GR のどちらかを満たせば利用可としてますので、一度認証してから購入キャンセルしてネットを切っておけば、その後1週間、かつ10回認証処理を通るまで利用できる事になりますね。ちょっと甘すぎませんかね?。まあ、スマホをネット断ちしてまで不正使用する人もそう居ないでしょうけど。

 今現在はどうか分かりませんが、購入直後は違う値を返したりもしてたらしいので、以前詳しく調べられてた方のサイトも紹介しておきます。
体験版でライセンス認証するとどうなるの?
 LVL 関連のドキュメントや記事を読んでいると、LVL は有料アプリでないと使えない 的な事がよく書かれてるのですが、具体的にどう使えないのかは書いてません。
 実際に試してみたところ、体験版などの無料アプリについてもサーバアクセスが拒否される訳ではなくて、有料アプリ同様、アプリが公開さえされてればちゃんと返答は返ってきます。
  • アプリを一度でも Google Play からインストールすると、そのアカウントでは LICENSED が返る。その際、VT には Long.MAX_VALUE が返り、次回認証は永遠に行われない。
  • アプリを Google Play からを一度もインストールした事がないアカウントでは、NOT_LICENSED が返る。
  • 一度でも Google Play からインストールすると、以後アンインストールしても Google Play に履歴は残るようで、その後はずっと LICENCED が返る。NOT_LICENSED の状態に戻す手段は見つけられなかった。
 ということで、体験版だからと言ってライセンス認証周りの処理を省かなくとも、初回起動時時に問題なくライセンス認証は通って、以後無期限で使えます。
 むしろ 体験版では認証処理を通らないようにする、とか余計な事をすると、有料版の認証を回避する糸口にされかねない ので、処理を変えない方がいいと思います。
LVL を自分のプロジェクトに含めてしまい隊
 悪い人がアプリを解析する際に、どのソースで認証処理をしているのか少しでも分かりにくくする手の一つとして、LVL 一式を app モジュール内に含めてしまう という手もあるのですが、単純にソースを移動してパッケージ名を変えただけではエラーが出ると思います。

 元のサンプルライブラリは、compileSdkVersion が 15 とかになってると思いますが、それを SDK 23 以上にすると、org.apache.http 関連のパッケージがない とか言い出します。どうも 23 で削除されたらしい。勝手だなぁ。
 で、今のところは build.gradle に以下のように書けばなんとかなる模様。
build.gradle
android {
    ...
    useLibrary 'org.apache.http.legacy'
    ...
 ちなみにまあ、app モジュールに混ぜたところで、アプリを解析する難易度がそれほど上がるとも思えないですけどね。でも何人かはそこで諦めるかもしれないし、そういう積み重ねが大切。
aidl ってなんなのよ?
 さて、LVL の library モジュールに含まれてはいるものの、「とりあえず要りません」 とか一蹴してた aidl ファイル なのですが、いったい何なんですかねコレ?
 どうも、アプリから他のプロセスなどに処理を依頼する際などに、本来は長々と定型処理を書かないといけないのを省力化してくれる、一種のスクリプトらしいですよ。
 本来は、aidl ファイルを記述すると、ビルド時に各モジュールの build/generated/source/aidl/ 以下に java のソースが自動生成されるので、アプリからはそれを import して使うらしいです。

 ただ、LVL に含まれる aidl ファイルについては、なぜかすでに、自動生成されたらしき java ファイルが、library モジュールのソースとして鎮座してます。
ILicensingService.java
ILicenseResultListener.java
 なので、そこに改めて aidl ファイルもプロジェクトに含めてしまうと、同名のクラスが生成される事になり、エラーになります。

 なんでこんななってるのか、正確なところは分かりませんが、元の aidl ファイルから自動生成されたソースをそのまま使うと、逆コンパイルした際に Google Play にアクセスしてる箇所がパッケージ名でバレバレなので、同パッケージにする事で難読化されるようにしてるのかな、と勝手に納得しました。もしくは、単に aidl が使えない開発環境への配慮か。

 ちなみに、2016/07 現在、 Android Studio 2.1 で実際に aidl から java を自動生成させてみて、LVL に元から含まれてる java ファイルと比べてみましたが、パッケージ名が変えられてるくらいで、実質的な内容は全く一緒でした。
 それと、Android Studio では、aidl ファイルはパッケージ名と同じディレクトリを掘って置かないとダメらしいです。java の無闇にディレクトリ堀る文化ホントキライ。
app/src/main/aidl/com/android/vending/licensing/ILicensingService.aidl
app/src/main/aidl/com/android/vending/licensing/ILicenseResultListener.aidl

posted by ひこざ at 21:44| Comment(0) | 開発 - Android
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。