How to protect Google Play public key when doing InApp Billing

13,368

Solution 1

This comes up a lot around here :) The idea behind the paragraph you are quoting is that for in-app billing to be secure, you need to verify transaction signatures. Those are signed with a private key, associated with your developer account. The key resides on Google's servers, so it's fairly safe to assume that no one else can sign data with it. To verify it you need your public key, which you can copy from the developer console. If someone replaced it in your app, they could fool it to accept in-app billing transactions from unauthorized sources, because if they plant the public key, they probably also control the corresponding private key. In practice however, it is far easier to simply modify your code in the right places to always return true for isLicensed(), hasItem() or similar methods you might have and no one does this.

The best way to protect the key is, of course, not to have the key in your app at all. Move all of the transaction validation logic to your server, and use HTTPS to connect to it. Properly validate the certificate chain to ensure you are talking to your own server(s). Otherwise, someone might mess around with DNS and fool your app to connect to their own servers. A similar attack against iOS purchasing was announced a couple of weeks ago.

The next best thing is to somehow obfuscate the key, and have it included in your app. This has the advantage that you don't need a server, but the disadvantage is that if someone is determined enough they will figure it out, since they can always reverse the byte code of your app. So your best bet is to come up with your own original way to do it that doesn't show up on public forums :) To make it a bit harder, you can implement the validation part in native code, which is harder (but not impossible) to analyze. Still, as mentioned above, patching byte code in the right places is far easier than trying to replace the public key, so that is what most crackers will do.

Solution 2

Do at least simple text transform. The idea is that plain dex disassembling won't reveal your public key.

Here's example of function that makes simple string encoding/decoding:

/**
 * Simple String transformation by XOR-ing all characters by value.
 */
static String stringTransform(String s, int i) {
   char[] chars = s.toCharArray();
   for(int j = 0; j<chars.length; j++)
      chars[j] = (char)(chars[j] ^ i);
   return String.valueOf(chars);
}

Then your private key is stored in source as encoded string (encode it with this function), and decoded at runtime with same function. This is kind of "XOR" method suggested by Google.

You make the 'i' parameter yourself, anything random such as 0x27 or other will work. If you hide more strings this way, use different 'i' for each transform.

Solution 3

As an alternative to obfuscating the key manually, you can also let an obfuscator do it automatically. ProGuard is part of the Android SDK, but it mostly obfuscates class/field/method names, not strings. Its specialized sibling for Android, DexGuard, can add more layers of obfuscation, applying string encryption, class encryption, and reflection. It's not free, but it saves time and it's probably more effective than doing it manually.

(I am the developer of ProGuard and DexGuard)

Solution 4

Store your public key in the server side and once you get response from google play to verify the key send that response to server and perform your operation there at server.

Solution 5

The public key is base64-encoded ([a-zA-Z0-9+/]) so you can easily avoid the need to escape the obfuscated string at all as is an annoying problem in @PointerNull's solution.

Instead, you can perform the obfuscation by first converting the character in question to a 6-bit int. Then do the bit manipulation (e.g. XOR-ing) and then convert back to a base64-encoded char. Guaranteed no character escaping needed.

Share:
13,368
Ryan
Author by

Ryan

Updated on August 08, 2022

Comments

  • Ryan
    Ryan almost 2 years

    Actually this is a little bit silly about protecting public key (what is the definition of public key then?) but as per the documentation by Google:

    To keep your public key safe from malicious users and hackers, do not embed it in any code as a literal string. Instead, construct the string at runtime from pieces or use bit manipulation (for example, XOR with some other string) to hide the actual key. The key itself is not secret information, but you do not want to make it easy for a hacker or malicious user to replace the public key with another key.

    Are there any recommended way to do it?

    I know there are many ways to do it, I just don't want to follow the same way how people handle password hashing in the past (e.g. md5, sha1 etc), I want to know the best practice in the above use case.

  • Pointer Null
    Pointer Null almost 12 years
    Mystery :) If you're programmer, you surely overcome such problem.
  • Stunner
    Stunner over 11 years
    What do you mean "In practice however, it is far easier to simply modify your code in the right places to always return true for isLicensed(), hasItem() or similar methods you might have and no one does this."? That sentence does not make complete sense. Please clarify.
  • Nikolay Elenkov
    Nikolay Elenkov over 11 years
    It means that it is easier to patch the app and outright disable any licensing checks you might have rather then try to replace the key and mess try to fool signature validation.
  • Dan J
    Dan J about 11 years
    @NPike, you'll need to escape the string to use it in Java. Try this website: htmlescape.net/javaescape_tool.html Unfortunately, I found the transform process would often result in a string that Eclipse couldn't cope with (even when escaped), so you'll have to tweak the value being XORed until you get something you can use...
  • IgorGanapolsky
    IgorGanapolsky almost 8 years
    ~"probably also control the corresponding private key". But In-App Billing requests go through Play Store APK on the device. So how would someone control or manipulate that logic??
  • IgorGanapolsky
    IgorGanapolsky almost 8 years
    Isn't DexGuard overkill for just obfuscating one simple string? I mean, what is the guarantee that it will do it right anyway?
  • IgorGanapolsky
    IgorGanapolsky almost 8 years
    That presumes one has such server-side infrastructure set up.
  • MHM
    MHM over 7 years
    @IgorGanapolsky When they build a new modified apk they can simulate purchase without going to Play Store (something like luckypatcher) and so, they can respond to your App a valid purchase string with a signature correspond to modified public key.