バックグラウンド:
現在GooglePlayで販売されているAndroidアプリがあります。アプリが機能するためには、ユーザーはアプリ内課金を介して「トークン」を購入する必要があります。「トークン」は消耗品であり、例えば一度使用して仕上げます。トークンを確認するために、Playストアから返された情報が有効であることを確認するために標準のJavaRSAセキュリティコードを使用するサーバーに購入データを送信します。(以下のコード)。アプリをリリースする前に広範なテストを行い、アプリがストアに公開された後でも、さらにいくつかのテストを行いました。Googleから返されるデータは、毎回検証に合格しました。その後、12月の初め頃、署名の検証が失敗し始めました。ストア内のコードやアプリは変更されておらず、サーバー上の確認コードは静的なままです。
コードをデバッグし、Playストアから返されるレシートデータと署名データを実行しましたが、実際に検証に失敗しました。何が変わったのか、検証が正常に機能していたのになぜ失敗し始めたのかを説明するのに迷っています。
質問:
変更されていないアプリで署名の検証が失敗した、これまでに誰かがこれに遭遇したことがありますか?問題がどこから来ているのかを試してみて、解決するためにどこから始めるべきかについてのヒントはありますか?
さらに詳しい情報
私が変更を考えることができる唯一のことは、Googleがアプリ内課金API v3をリリースしたことですが、それは私たちが使用しているV2には影響しないはずです。
開発を支援するために、net.robotmedia.billingライブラリを使用してIABを処理します。
以下は、Playストアから返されたデータのサーバー検証コードです
ここで、encodePublicKey=>Playストアからの公開鍵
signedData=>Playストア購入からの返品としてbase64でエンコードされたreceiptData
署名=>Playストアから返された署名
public class Security {
public final static Logger logger = Logger.getLogger(Security.class.getName());
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey
* Base64-encoded public key
* @throws IllegalArgumentException
* if encodedPublicKey is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
catch (InvalidKeySpecException e) {
logger.error("Invalid key specification.", e);
throw new IllegalArgumentException(e);
}
catch (Base64DecoderException e) {
logger.error("Base64 decoding failed.", e);
throw new IllegalArgumentException(e);
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey
* public key associated with the developer account
* @param signedData
* signed data from server
* @param signature
* server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
byte[] decodedSig = Base64.decode(signature);
if (!sig.verify(decodedSig)) {
logger.error("Signature verification failed.");
return false;
}
return true;
}
catch (NoSuchAlgorithmException e) {
logger.error("NoSuchAlgorithmException.");
}
catch (InvalidKeyException e) {
logger.error("Invalid key specification.");
}
catch (SignatureException e) {
logger.error("Signature exception.");
}
catch (Base64DecoderException e) {
logger.error("Base64 decoding failed.");
}
return false;
}
}