WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

Androidアプリ内課金の不正購入チェックをPHPで

サーバー担当の山内です。

今回は、サーバー側のAndroidアプリ内課金についてです。
アプリ側の課金については、当ブログの下記エントリをご覧ください。

AndroidにおけるConsumableタイプのアプリ内課金

本エントリでは、安全性を高めるためサーバー側で不正購入のチェックをします。

大まかな流れはこうです。
1. 「購入情報(json形式)」と秘密鍵で「暗号化された署名(Base64形式)」の2つを受け取る
2. 「購入情報のSHA1ハッシュ値」と「公開鍵」で「復号化した署名」を照合させる

「公開鍵」を使用するために用意するものは、PEM形式の証明書です。まずDeveloper Console上で取得したDER(Distinguished Encoding Rules)形式を、PEM(Privacy Enhanced Mail)形式に変換したものを用います。PEMはBase64エンコードされたデータを含み、ヘッダーとフッターを付加したフォーマットですので、ヘッダーとフッターの間にBase64エンコードされた文字列が入っていればOKです。ヘッダーとフッターは次のものを利用します。

-----BEGIN PUBLIC KEY-----  
-----END PUBLIC KEY-----  

ちなみに、秘密鍵の場合は次のものを利用します。

-----BEGIN RSA PRIVATE KEY-----  
-----END RSA PRIVATE KEY-----  

RFC 1421の規約によれば、PEMのBase64エンコードの行最大長は64なので、PHPのビルトイン関数であるchunk_splitを用いて、文字列を64文字ごとに改行(分割)させる必要があります。chunk_splitの第2引数に64を、第3引数にPHP_EOL定数(OSに依存しない改行コード)を指定することで、PEM形式の証明書を得ることができます。この証明書をopenssl_get_publickeyに渡せば、PHPで公開鍵を生成することができます。

ここでは「購入情報」を$signed_data、「暗号化された署名」を$signatureとします。

$signed_data = '{"orderId":"12999763161054705751.2363231872472291",  
"packageName":"jp.wonderplanet.zuma","productId":"jp.wonderplanet.zuma",  
"purchaseTime":1366356881000,"purchaseState":0,  
"purchaseToken":"xpvtbbfdjbnpbrhaunfvzqrf.AO-J1OxcEyM05mHhDPKewcCDyXfGx  
9af8NR4VwVVSPJH0T0gTvswikPkGO6ASziXkWkslmZtGRXvKvZa_AHdTEXpDpX7naJdPTWU  
3f07l1Y5uNJ6hluDwUJbOgRILqQvMGaUa0LeLC-_aORWGUuOeU5NQWDrqiweQl"}';  
  
$signature = 'KufRryqT0TFlxMv\/4YQh3548wv3weaqM6YdK\/c67yR5PPqyTrfM  
0Tk9DuIdilSTb6oX54c6U5xx+TciaikTq45CCvk\/PTIbR\/KwmsoK8r7tvi9PTEavA  
9I\/iKtAdi\/vxOKmyt1TZBVRdKid\/PF9MkCVhffllcTwh6HD3ikTXaZLeXfmYBtAU  
AO8zXXQ2BlcLwukKcLkzuQHNYef31FDamoqVyDTCbOqq3KUDAa\/+OKQT06qA5zwTlb  
Io2R3Kp0oYXVOSbYwKK929cKIe0o\/1mzApZCaFSlYlDtRTOoTpKDTmxUQdKiKhkVYs  
FSLvKCR3wQUiYDF4Xb8FktDTi9QvwA==';  
  
// RSA(PEM形式)公開鍵の生成  
$cert = "-----BEGIN PUBLIC KEY-----" . PHP_EOL .  
        chunk_split($rsa_key, 64, PHP_EOL) .  
        "-----END PUBLIC KEY-----";  
  
// openssl_get_publickey()はopenssl_pkey_get_public()のエイリアス  
$pubkey = openssl_get_publickey($cert);  
  
// 署名をBase64デコードする(バイナリになる)  
$signature = base64_decode($signature);  
  
// openssl_verify()のデフォルトアルゴリズムはSHA1なので、  
// OPENSSL_ALGO_SHA1は渡さなくてもよい  
$result = openssl_verify($signed_data, $signature, $pubkey, OPENSSL_ALGO_SHA1);  
  
// キーをメモリから解放  
openssl_free_key($pubkeyid);  

openssl_verify()は、成功すると1を返します。
このケースでは変数$resultの値が1以外であれば不正購入の疑いがあるということになります。