サーバー担当の山内です。
今回は、サーバー側のAndroidアプリ内課金についてです。
アプリ側の課金については、当ブログの下記エントリをご覧ください。
AndroidにおけるConsumableタイプのアプリ内課金
本エントリでは、安全性を高めるためサーバー側で不正購入のチェックをします。
大まかな流れはこうです。
- 「購入情報(json形式)」と秘密鍵で「暗号化された署名(Base64形式)」の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以外であれば不正購入の疑いがあるということになります。