Android In App Billing: securing application public key
Asked Answered
R

7

61

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?

Restorative answered 16/1, 2013 at 6:47 Comment(4)
Why would one hide a public key, because, well it's public?Clincher
@Clincher well it is the first question I asked myself.. But I think we are not talking about public key in a key pair (private key/public key pair).. Application public key may be different.. Too bad Google doesn't have much documentation about it especially after they themselves think that it is a security threat..Restorative
'A string' XOR 'secret' becomes 'Another string'. 'Another string' XOR the same 'secret' again, it becomes back to 'A string'. That's one XOR use case.Strumpet
@Restorative it helps prevent the attacker from easily replacing your public key with his own and verifying the purchase with his own server (with his private key of course), so performing such tricks makes the attacker work just a little harder. Of course to really mitigate this problem, secondary verification on your own servers is encouraged...Overtly
B
43

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.

Bobbe answered 16/1, 2013 at 7:56 Comment(8)
What if the app is open source? Is this really worth it? Is there some other way to "secure" the public key?Iridic
There really is not any way to "secure" the public key. If your software can reconstruct the key from data stored in the APK, then so can software that just reads the APK. Chances are pretty good that it's not really worth it to do anything much more than trivial obscuring of the key. Kiddie hackers have broken stronger software protection schemes than you're ever likely to come up with. If you prevent use of automated software that extracts the keys from any APK that copied the SDK code verbatim, you've probably done your duty.Bobbe
One more helpful answer on the security stackexchange! security.stackexchange.com/questions/33686/…Overtly
@ibgib: If your app is open source, then you won't be able to make purchases on Google play, for this and whole slew of other reasons as well, like (for example) the APK not being marked as having been installed by the Google Play installer. IF you want donations from an open source app, then you're probably better off using PayPal-hosted credit card payments. They can do micro-transactions for a significantly lower fee as well. (Google Play apps are forbidden from using alternate forms of payment by the terms of service, in case you though there was a germ of a great idea there. Don't do it!)Bobbe
@RobinDavies We've moved away from anything involving in-app purchases. Thanks for the heads up though.Iridic
@RobinDavies it's easier for evil people to bypass signature checking if it can replace public key. in this scenario those guys don't even make signature with their private key. public key is PUBLIC. hiding public key can be called anything but security.Swirsky
But can't the hacker just look at the string value regardless of the fact that it is split up into parts?Whimsical
The issue is whether hackers can write automated tools to crack thousands of apps in bulk, or whether they have to manually do each one using a debugger. If each app assembles their license string using eccentric methods, it becomes harder (but not impossible) for a hacker to write an automated to tool to crack a paid app.Bobbe
R
13

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.

Rowdyish answered 20/2, 2015 at 10:41 Comment(6)
~"copy the result from the debugger and paste it into code". Why the manual step? Can't you do this in Java?Finlay
The whole point is you don't want to have your real public key in plain text; so in the above it starts off in plain text; the debugger tells you what the encoded version is; then you paste the encoded version over the plain text version.Rowdyish
But when you pass your public key into IabHelper's constructor, that key is your original public key anyway. And as such, that specific call stack can be reverse-engineered.Finlay
Correct: if your goal is to make it impossible to gain access to the key, you will never succeed. It is called a public key for a reason. The recommendation Google give is to avoid leaving your key in plain text, so doing a simple transform, as above, achieves this. It does not stop anyone who is determined, but it avoids the key coming up as a string literal and some of the more basic attack vectors.Rowdyish
Doesn't your solution involve the key as a string literal in the source code?Encephalon
No, in as far as you put the key in, run the code to transform it, and then replace it with the transformed version.Rowdyish
F
5

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.

Forecast answered 16/1, 2013 at 7:38 Comment(5)
IF you are using ProGuard to obfuscate your app, then this trick will be useless. ProGuard will re-factor them to be one String again.Morisco
Indeed. In addition, I would caution against using IabHelper as is.. passing the key (regardless of how it was generally dynamically) via the constructor means that if the attacker can identify the IabHelper class (even if it may be obfuscated), then he knows exactly where to insert his own malicious key (just seems a bit too easy)Overtly
@KevinLee How would you modify the IabHelper then?Finlay
@Finlay hardcode the key within IabHelper as a class variable, or put it exactly where it is it will be used. Or split the key into byte arrays. As for the methods and callbacks, you do have to spend some time understanding them and reimplementing the stuff that is needed. Take note of hardcoded string values as they tend to give away a lot of information; make them less descriptive, or use error codes. Possibly invert simple booleans or replace them with a comparison to some encoded value, etc. The idea is to make your code somewhat different.Overtly
@KevinLee but there is still no point in this, cause you still will use boolean to enable or disable (if user purchased or not) some of your app premium features, so it's not even about purchases/keys but your functions to unlock something where you will use result (boolean) from purchase apiDeas
C
2

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};
}
Cephalad answered 20/12, 2016 at 9:12 Comment(0)
K
1

Building on Steven Craft's Answer with help i got from gidds

Here's a cleaner code. In addition it swaps out digit (0-9) for ascii character (37-46) and vice versa. Written in Kotlin.

 val string = "Hello World 012345679 %&()"
 fun String.swapCase() = map {
        when {
            it.isUpperCase() -> it.toLowerCase()
            it.isLowerCase() -> it.toUpperCase()
            it.isDigit() -> (37 + (it.toInt() - 48)).toChar()
            it.isDefined() -> if (it.toInt() >= 37 && it.toInt() <= 46) (48 + (it.toInt() - 37)).toChar() else it
            else -> it
        }
    }.joinToString("")
    println(string.swapCase()) // hELLO wORLD %&'()*+,. 0134

Use this -> https://edge-developer.github.io/BillingGenerator/ to generate all those in a fly

Katti answered 7/4, 2019 at 11:43 Comment(0)
C
0

Follow 3 simple steps to secure API/Secret key

We can use Gradle to secure the API key or Secret key. Check my answer.

Coverley answered 29/11, 2017 at 12:57 Comment(0)
M
-3

Is someone is really need you private key? I think the whole idea is replace it. IMHO any manipulations are useless. The only thing to do by evil person is just initialize variable with correct (his own key) value one line begore google API call.

Morion answered 16/4, 2016 at 22:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.