In-app Billingでsignatureの検証をサーバサイドで行う

Androidのアプリ内課金を安全に使うためには何らかの防御策を取らないといけないのですが、その方法の一つとしてsignatureの検証があります。
購入完了後にMarketから購入情報がjsonで送信されてきます。

{"nonce":2102159826496562602,"orders":[{"notificationId":"2764732181918735088",
"orderId":"259065433347272","packageName":"com.matabii.test.billing1",
"productId":"product1","purchaseTime":1304573886000,"purchaseState":0}]}

これとセットで、jsonのsignatureも送られてきます。

YG7sJWdElGYBTkmRimrek0tZ7Y7WISxH8hAZwrpu/i4bfsR22PQ5ODjWHF9Z9RxmktMpQY/y6IxV
yaIs4lR1v5GNCXt3PHaIPo2lSjrwlHmyczs+YiD+DdbG6rHCoVuag7le5+5APLCGha9cpikFAZCX6D
YVY2kD1w7svyU6HxdsPAtU5HGdMEdK+CIsfDg+ALWIJ/rSr9WMvT/iMYjiCRhYMuy
c6sCamS9Ag7kpZqtHYbCCC0S9FdM5VMCzbY/BxA+4LYdq/cJL0+dVIElfWnIk/xzwBQ60IAicZX9D
S4zYZc9WnvgQhjTjKkj5l4luwy825jyAInhXagdDCN3qtg==

このsignatureはAndroid Marketにて、パブリッシャーの秘密鍵を使って生成されています。秘密鍵はアプリの作者であっても知ることが出来ませんが、対応する公開鍵はパブリッシャーサイトにログインすれば分かります。
http://developer.android.com/images/billing_public_key.png

BillingのサンプルアプリにはJavaを使った検証コードが書いてありますが、アプリの中に検証コードと公開鍵を埋め込むのはセキュリティ上よろしくなく、可能であればリモートサーバを使って検証するべし!と公式にも書かれているので、サーバサイドでPHP+opensslを使って検証してみます。

opensslで検証する

まずはPHPを使わず、Linuxサーバでopensslを使って検証してみます。opensslでやり方が分かれば、PerlPythonを使うにしても同じだと思うので。

公開鍵を利用出来る形式にする

パブリッシャーサイトで得られる公開鍵ですが、これはBase64 エンコードの RSA 公開鍵です。と書かれています。暗号化について人に説明出来るほどの知識は無いので省略しますが、鍵のフォーマットにも色々あって、opensslが理解出来るPEMかDERに変換します。
まずは公開鍵をコピペしてテキストファイルとして保存します。空白や改行コードが混じらないように注意して下さい。どうしても上手くいかない場合は、バイナリエディタで確認してみましょう。ここでは「public」というファイル名で保存したとします。
base64エンコードされてるということなので、base64デコードをかけます。

base64 -d public > public.der

base64デコードすれば、DER形式として使える公開鍵になります。これをPEM形式にするにはこのpublic.derを利用します。

openssl rsa -inform DER -outform PEM -pubin -in public.der -out public.pem

これでPEM形式の公開鍵が得られます。opensslコマンドは難解ですね・・・

signatureもデコードする

Jsonについてきたsignatureもbase64エンコードされているので、デコードしておきます。signatureというファイル名でテキスト保存しているとして

base64 -d signature > signature.bin
検証する

これで役者が揃いました。signature.binとpublic.derを使ってMarketから送信されてきたjsonを検証してみます。jsonについては「signeddata」というファイル名で保存されているとします。
暗号化の方法はRSA公開鍵方式ですが、暗号化についてはsha1で行なっているようです。
なので検証用のopensslコマンドは、

openssl dgst -sha1 -keyform DER -verify public.der -signature signature.bin  signeddata 

「Verified OK」と表示されると成功です。試しにsigneddataのjsonを改ざんしてみると「Verification Failure」になりました。
ちなみにPEM形式の鍵、public.pemを使うには以下のようにします。

openssl dgst -d -sha1 -keyform PEM -verify public.pem -signature signature.bin  signeddata

pem鍵を用意する理由ですが、PHPのopenssl関数がPEM形式しか受け付けてくれなかったからです。

PHPで検証する

PHPは--with-openssl付きでコンパイルしていないといけません。

<?php
$data = file_get_contents("signeddata");
$signature = file_get_contents("signature.bin");
$pubkey = file_get_contents("public.pem");
$pubkeyid = openssl_get_publickey($pubkey);

$ok = openssl_verify($data, $signature, $pubkeyid);
var_dump($ok);