When using in-app billing, you should verify the purchase data, by checking if the INAPP_PURCHASE_DATA was signed with the INAPP_DATA_SIGNATURE by using the base64 encoded public key from Google Play store.
See the explanation of INAPP_PURCHASE_DATA and INAPP_DATA_SIGNATURE here.
There is a Security class you can use to verify the purchase:
public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
/**
* 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, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
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) {
byte[] signatureBytes;
try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Base64 decoding failed.");
return false;
}
try {
Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(signatureBytes)) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
}
return false;
}
}
You have to invoke the verifyPurchase
and pass the purchase data, the given signature and the base64PublicKey public key from Google Play. There is a wrapper implementation I can use for the purchase flow in my app.
If you look into the IabHelper implementation, they pass the public key for the verification in constructor. The documentation of the ctor says:
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
I guess they mean the Base64-encoded RSA public key in Licensing & in-app billing section in Google Play:
Maybe I don't know enough about cryptography but how is this possible that I use a public key from Google Play to check an encryption which is supposedly made with my "developers private key" (see explanation in first link). Do they mean my "private key I used to sign the app"? I don't think so, because they cannot know my (local) private key (I use to sign my app) and what does it have to do with this public key from Google Play, so what do they mean with "developer's private key".
So my questions are:
Did I understand it right that the public key is the key from Licensing & in-app billing?
Do I also need to add licensing to my app to get this verification working or should this work "out of the box", so can I omit this step?
What is the "developer's private key" Google is using to sign the purchase data and where do I see it? (I need to run some Unit tests on my server to check my implementation and I want to encrypt the INAPP_PURCHASE_DATA to get the INAPP_DATA_SIGNATURE as well to be able to get a valid security check if I verify it with the given public key.
[UPDATE]. Obviously, the private key is hidden:
The Google Play Console exposes the public key for licensing to any developer signed in to the Play Console, but it keeps the private key hidden from all users in a secure location.
See: https://developer.android.com/google/play/licensing/adding-licensing.html