Android In App Billing: securing application public key

From Android In App Billing version 3 (TrivialDrive)sample application coming with sdk

MainActivity.java

/* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
 * (that you got from the Google Play developer console). This is not your
 * developer public key, it's the *app-specific* public key.
 *
 * Instead of just storing the entire literal string here embedded in the
 * program,  construct the key 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 we don't
 * want to make it easy for an attacker to replace the public key with one
 * of their own and then fake messages from the server.
 */
String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";

Well I am not sure I understand this security measure. I know how to get the application public key (which is already base 64 encoded) from Google Play Developer Console.

What I am not understanding is this part

 /* Instead of just storing the entire literal string here embedded in the
 * program,  construct the key at runtime from pieces or
 * use bit manipulation (for example, XOR with some other string) to hide
 * the actual key
 */

As far as I know, this public key is a constant string, which is given from Google during application upload process.

How can we create the same key programmatically using any bit manipulation process? Has someone done it before? Is there any sample code on how to do this?


Something like this:

String Base64EncodedPublicKey key = "Ak3jfkd" + GetMiddleBit() + "D349824";

or

String Base64EncodedPublicKey key = 
         DecrementEachletter("Bl4kgle") + GetMiddleBit() + ReverseString("D349824");

or anything that doesn't put the key in base64 plaintext in a single string. Probably also something that doesn't store the key in base64 would be a good idea too, since raw base64 text fragments are pretty easy to spot.

It's not a particularly GOOD way to protect the key. But it protects against a trivial attack where somebody just searches through literal strings in you APK looking for something that looks like a base64-encoded public key. At least you make the #$#$ers work a little bit.

Presumably evil people can do bad things if they identify your public key. Google seems to think so, apparently. I can guess what this step does, but I'm not sure I really want to speculate on that in an open forum, and give anyone any ideas. You want to do it though.

The basic plot summary would be that you're making it more difficult for somebody to write an application that programmatically de-LVLs an applciation.

One assumes that anyone who's doing this makes a living cracking 20 or 30,000 android apps and republishing them. Chances are, I suppose that they're not going to take the extra ten minutes to add your app to the list of 20,000 Android apps that have already been broken by a program, if they actually have to do a little bit of manual work. Unless you have a top tier application. And then the battle is potentially endless, and probably ultimately futile.

Splitting the key into consecutive chunks (as proposed in another answer) probably isn't good enough. Because the key will end up in consecutive strings in the string constant tables in the APK. Too easy to find that with a program.


An alternative is to do some basic transforms on the key.

// Replace this with your encoded key.
String base64EncodedPublicKey = "";

// Get byte sequence to play with.
byte[] bytes = base64EncodedPublicKey.getBytes();

// Swap upper and lower case letters.
for (int i = 0; i < bytes.length; i++) {
    if(bytes[i] >= 'A' && bytes[i] <= 'Z')
        bytes[i] = (byte)( 'a' + (bytes[i] - 'A'));
    else if(bytes[i] >= 'a' && bytes[i] <= 'z')
        bytes[i] = (byte)( 'A' + (bytes[i] - 'a'));
}

// Assign back to string.
base64EncodedPublicKey = new String( bytes );

So the idea would be to put your original key in as base64EncodedPublicKey and run the above code, it would swap lower and uppercase letters and put the result back in base64EncodedPublicKey. You can then copy the result from the debugger and paste it into code as the original base64EncodedPublicKey value. At this point your key will be transformed (upper and lower case switched) and at runtime it'll fix it back to the correct casing, and continue to work.

The above is obviously quite a basic transcode, but you can be more creative, reverse the ordering of A-Z, swap odd and even numbers, swap vowels for even numbers. The issue here is that if I put code in the above snippet that does a bunch of more interesting transcodes, and then everyone copy and pastes that into their projects, a cracker will easily be able to see and use the transcode themselves (from looking at this post)! So you just have to come up with a few transforms yourself.

I've purposely made the above work in both direction (so if you run it twice, you'll get your original value back) as it makes it easy to run the algorithm on your original key. I think it is kind of neat it looks like the real key is sitting there as plain text, a casual cracker may try to switch this and then be confused when it doesn't work.


You can split it into pieces like this

String piece1 = "SDFGJKGB4UIH234WE/FRT23RSDF/3DFUISDFVWE";
String piece2 = "SDFGJKGB4UIHUISDFVWE";
String piece3 = "BDYASGBDNAWGRET24IYE23das4saGBENWKD";
String piece4 = "432423SDF23R/+SDDS";

mHelper = new IabHelper(this, piece1 + piece2 + piece3 + piece4);

Any kind of manipulations will do.

You can't hide the public key perfectly from the attacker, you just need to manipulate the string to confuse a attacker a little bit

You can add some strings and remove it when it's needed or split it into chunks.


What I did was to transform the key into a char array, split it in two and then reconstruct it when needed like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_shop);

    char[] base64KeyByteArray = ArrayUtils.addAll(getPublicKeyChunk1(), getPublicKeyChunk2());

    Log.d(TAG, String.valueOf(base64KeyByteArray));
}

private char[] getPublicKeyChunk1() {
    return new char[]{82, 73, 67, 66, 73, 106, 65, 78, 66, 103, 107, 113, 104, 107,
            105, 71, 57, 119, 79, 66, 65, 81, 69, 70, 65, 65, 79, 67, 65, 81, 56, 65, 77, 73,
            73, 66, 67, 103, 75, 67, 65, 81, 69, 65, 121, 55, 81, 76, 122, 67, 105, 80, 65,
            110, 105, 101, 72, 66, 53, 57};
}

private char[] getPublicKeyChunk2() {
    return new char[]{82, 43, 68, 47, 79, 121, 122, 110, 85, 67, 118, 89, 108, 120, 43, 49,
            80, 100, 67, 108, 55, 90, 57, 103, 119, 57, 87, 78, 79, 111, 53, 101, 80, 71,
            117, 74, 104, 82, 87, 97, 100};
}