Best practice for storing and protecting private API keys in applications

AndroidReverse EngineeringProguardApi Key

Android Problem Overview


Most app developers will integrate some third party libraries into their apps. If it's to access a service, such as Dropbox or YouTube, or for logging crashes. The number of third party libraries and services is staggering. Most of those libraries and services are integrated by somehow authenticating with the service, most of the time, this happens through an API key. For security purposes, services usually generate a public and private, often also referred to as secret, key. Unfortunately, in order to connect to the services, this private key must be used to authenticate and hence, probably be part of the application. Needless to say, that this faces in immense security problem. Public and private API keys can be extracted from APKs in a matter of minutes and can easily be automated.

Assuming I have something similar to this, how can I protect the secret key:

public class DropboxService  {

	private final static String APP_KEY = "jk433g34hg3";
	private final static String APP_SECRET = "987dwdqwdqw90";
	private final static AccessType ACCESS_TYPE = AccessType.DROPBOX;

	// SOME MORE CODE HERE
	
}

What is in your opinion the best and most secure way to store the private key? Obfuscation, encryption, what do you think?

Android Solutions


Solution 1 - Android

  1. As it is, your compiled application contains the key strings, but also the constant names APP_KEY and APP_SECRET. Extracting keys from such self-documenting code is trivial, for instance with the standard Android tool dx.

  2. You can apply ProGuard. It will leave the key strings untouched, but it will remove the constant names. It will also rename classes and methods with short, meaningless names, where ever possible. Extracting the keys then takes some more time, for figuring out which string serves which purpose.

    Note that setting up ProGuard shouldn't be as difficult as you fear. To begin with, you only need to enable ProGuard, as documented in project.properties. If there are any problems with third-party libraries, you may need to suppress some warnings and/or prevent them from being obfuscated, in proguard-project.txt. For instance:

    -dontwarn com.dropbox.**
    -keep class com.dropbox.** { *; }
    

    This is a brute-force approach; you can refine such configuration once the processed application works.

  3. You can obfuscate the strings manually in your code, for instance with a Base64 encoding or preferably with something more complicated; maybe even native code. A hacker will then have to statically reverse-engineer your encoding or dynamically intercept the decoding in the proper place.

  4. You can apply a commercial obfuscator, like ProGuard's specialized sibling DexGuard. It can additionally encrypt/obfuscate the strings and classes for you. Extracting the keys then takes even more time and expertise.

  5. You might be able to run parts of your application on your own server. If you can keep the keys there, they are safe.

In the end, it's an economic trade-off that you have to make: how important are the keys, how much time or software can you afford, how sophisticated are the hackers who are interested in the keys, how much time will they want to spend, how much worth is a delay before the keys are hacked, on what scale will any successful hackers distribute the keys, etc. Small pieces of information like keys are more difficult to protect than entire applications. Intrinsically, nothing on the client-side is unbreakable, but you can certainly raise the bar.

(I am the developer of ProGuard and DexGuard)

Solution 2 - Android

Few ideas, in my opinion only first one gives some guarantee:

  1. Keep your secrets on some server on internet, and when needed just grab them and use. If user is about to use dropbox then nothing stops you from making request to your site and get your secret key.

  2. Put your secrets in jni code, add some variable code to make your libraries bigger and more difficult to decompile. You might also split key string in few parts and keep them in various places.

  3. use obfuscator, also put in code hashed secret and later on unhash it when needed to use.

  4. Put your secret key as last pixels of one of your image in assets. Then when needed read it in your code. Obfuscating your code should help hide code that will read it.

If you want to have a quick look at how easy it is to read you apk code then grab APKAnalyser:

http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/

Solution 3 - Android

Another approach is to not have the secret on the device in the first place! See [Mobile API Security Techniques][1] (especially part 3).

Using the time honored tradition of indirection, share the secret between your API endpoint and an app authentication service.

When your client wants to make an API call, it asks the app auth service to authenticate it (using strong remote attestation techniques), and it receives a time limited (usually JWT) token signed by the secret.

The token is sent with each API call where the endpoint can verify its signature before acting on the request.

The actual secret is never present on the device; in fact, the app never has any idea if it is valid or not, it juts requests authentication and passes on the resulting token. As a nice benefit from indirection, if you ever want to change the secret, you can do so without requiring users to update their installed apps.

So if you want to protect your secret, not having it in your app in the first place is a pretty good way to go.

[1]: https://hackernoon.com/mobile-api-security-techniques-682a5da4fe10 "Mobile API Security Techniques"

Solution 4 - Android

Old unsecured way:

Follow 3 simple steps to secure the API/Secret key (Old answer)

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

1. gradle.properties (Project properties): Create variable with key.

GoogleAPIKey = "Your API/Secret Key"

2. build.gradle (Module: app) : Set variable in build.gradle to access it in activity or fragment. Add below code to buildTypes {}.

buildTypes.each {
    it.buildConfigField 'String', 'GoogleSecAPIKEY', GoolgeAPIKey
}

3. Access it in Activity/Fragment by app's BuildConfig:

BuildConfig.GoogleSecAPIKEY

Update:

The above solution is helpful in the open-source project to commit over Git. (Thanks to David Rawson and riyaz-ali for your comment).

As per Matthew and Pablo Cegarra's comments, the above way is not secure and Decompiler will allow someone to view the BuildConfig with our secret keys.

Solution:

We can use NDK to Secure API Keys. We can store keys in the native C/C++ class and access them in our Java classes.

Please follow this blog to secure API keys using NDK.

A follow-up on how to store tokens securely in Android

Solution 5 - Android

Adding to @Manohar Reddy solution, firebase Database or firebase RemoteConfig (with Null default value) can be used:

  1. Cipher your keys
  2. Store it in firebase database
  3. Get it during App startup or whenever required
  4. decipher keys and use it

What is different in this solution?

  • no credintials for firebase
  • firebase access is protected so only app with signed certificate have privilege to make API calls
  • ciphering/deciphering to prevent middle man interception. However calls already https to firebase

Solution 6 - Android

> The App-Secret key should be kept private - but when releasing the app > they can be reversed by some guys.

for those guys it will not hide, lock the either the ProGuard the code. It is a refactor and some payed obfuscators are inserting a few bitwise operators to get back the jk433g34hg3 String. You can make 5 -15 min longer the hacking if you work 3 days :)

Best way is to keep it as it is, imho.

Even if you store at server side( your PC ) the key can be hacked and printed out. Maybe this takes the longest? Anyhow it is a matter of few minutes or a few hours in best case.

A normal user will not decompile your code.

Solution 7 - Android

One possible solution is to encode the data in your app and use decoding at runtime (when you want to use that data). I also recommend to use progaurd to make it hard to read and understand the decompiled source code of your app . for example I put a encoded key in the app and then used a decode method in my app to decode my secret keys at runtime:

// "the real string is: "mypassword" "; 
//encoded 2 times with an algorithm or you can encode with other algorithms too
public String getClientSecret() {
	return Utils.decode(Utils
			.decode("Ylhsd1lYTnpkMjl5WkE9PQ=="));
}

Decompiled source code of a proguarded app is this:

 public String c()
 {
    return com.myrpoject.mypackage.g.h.a(com.myrpoject.mypackage.g.h.a("Ylhsd1lYTnpkMjl5WkE9PQ=="));
  }

At least it's complicated enough for me. this is the way I do when I have no choice but store a value in my application. Of course we all know It's not the best way but it works for me.

/**
 * @param input
 * @return decoded string
 */
public static String decode(String input) {
	// Receiving side
	String text = "";
	try {
		byte[] data = Decoder.decode(input);
		text = new String(data, "UTF-8");
		return text;
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}
	return "Error";
}

Decompiled version:

 public static String a(String paramString)
  {
    try
    {
      str = new String(a.a(paramString), "UTF-8");
      return str;
    }
    catch (UnsupportedEncodingException localUnsupportedEncodingException)
    {
      while (true)
      {
        localUnsupportedEncodingException.printStackTrace();
        String str = "Error";
      }
    }
  }

and you can find so many encryptor classes with a little search in google.

Solution 8 - Android

This example has a number of different aspects to it. I will mention a couple of points that I don't think have been explicitly covered elsewhere.

Protecting the secret in transit

The first thing to note is that accessing the dropbox API using their [app authentication][1] mechanism requires you to transmit your key and secret. The connection is HTTPS which means that you can't intercept the traffic without knowing the TLS certificate. This is to prevent a person intercepting and reading the packets on their journey from the mobile device to the server. For normal users it is a really good way of ensuring the privacy of their traffic.

What it is not good at, is preventing a malicious person downloading the app and inspecting the traffic. It is really easy to use a man-in-the-middle proxy for all traffic into and out of a mobile device. It would require no disassembly or reverse engineering of code to extract the app key and secret in this case due to the nature of the Dropbox API.

You could do [pinning][2] which checks that the TLS certificate you receive from the server is the one you expect. This adds a check to the client and makes it more difficult to intercept the traffic. This would make it harder to inspect the traffic in flight, but the pinning check happens in the client, so it would likely still be possible to disable the pinning test. It does make it harder though.

Protecting the secret at rest

As a first step, using something like [proguard][3] will help to make it less obvious where any secrets are held. You could also use the NDK to store the key and secret and send requests directly, which would greatly reduce the number of people with the appropriate skills to extract the information. Further obfuscation can be achieved by not storing the values directly in memory for any length of time, you can encrypt them and decrypt them just before use as suggested by another answer.

More advanced options

If you are now paranoid about putting the secret anywhere in your app, and you have time and money to invest in more comprehensive solutions, then you might consider storing the credentials on your servers (presuming you have any). This would increase the latency of any calls to the API, as it will have to communicate via your server, and might increase the costs of running your service due to increased data throughput.

You then have to decide how best to communicate with your servers to ensure they are protected. This is important to prevent all of the same problems coming up again with your internal API. The best rule of thumb I can give is to not transmit any secret directly because of the man-in-the-middle threat. Instead you can sign the traffic using your secret and verify the integrity of any requests that come to your server. One standard way of doing this is to compute an HMAC of the message keyed on a secret. I work at a company that has a security product that also operates in this field which is why this sort of stuff interests me. In fact, here is a [blog][4] article from one of my colleagues that goes over most of this.

How much should I do?

With any security advice like this you need to make a cost/benefit decision about how hard you want to make it for someone to break in. If you are a bank protecting millions of customers your budget is totally different to someone supporting an app in their spare time. It is virtually impossible to prevent someone from breaking your security, but in practice few people need all of the bells and whistles and with some basic precautions you can get a long way.

[1]: https://www.dropbox.com/developers/reference/auth-types#app "app authentication" [2]: https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning [3]: https://developer.android.com/studio/build/shrink-code.html [4]: https://www.approov.io/blog/simple-app-authentication.html

Solution 9 - Android

Whatever you do to secure your secret keys is not going to be a real solution. If developer can decompile the application there is no way to secure the key, hiding the key is just security by obscurity and so is code obfuscation. Problem with securing a secret key is that in order to secure it you have to use another key and that key needs to also be secured. Think of a key hidden in a box that is locked with a key. You place a box inside a room and lock the room. You are left with another key to secure. And that key is still going to be hardcoded inside your application.

So unless the user enters a PIN or a phrase there is no way to hide the key. But to do that you would have to have a scheme for managing PINs happening out of band, which means through a different channel. Certainly not practical for securing keys for services like Google APIs.

Solution 10 - Android

The most secure solution is to keep your keys on a server and route all requests needing that key through your server. That way the key never leaves your server, so as long as your server is secure then so is your key. Of course there is a performance cost with this solution.

Solution 11 - Android

Keep the secret in firebase database and get from it when app starts , It is far better than calling a web service .

Solution 12 - Android

Ages old post, but still good enough. I think hiding it in an .so library would be great, using NDK and C++ of course. .so files can be viewed in a hex editor, but good luck decompiling that :P

Solution 13 - Android

The only true way to keep these private is to keep them on your server, and have the app send whatever it is to the server, and the server interacts with Dropbox. That way you NEVER distribute your private key in any format.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBasic CoderView Question on Stackoverflow
Solution 1 - AndroidEric LafortuneView Answer on Stackoverflow
Solution 2 - AndroidmarcinjView Answer on Stackoverflow
Solution 3 - AndroidSkip HovsmithView Answer on Stackoverflow
Solution 4 - AndroidSANATView Answer on Stackoverflow
Solution 5 - AndroidAyman Al-AbsiView Answer on Stackoverflow
Solution 6 - Androiduser529543View Answer on Stackoverflow
Solution 7 - AndroidMilad FaridniaView Answer on Stackoverflow
Solution 8 - AndroidThePragmatistView Answer on Stackoverflow
Solution 9 - Androiduser1611728View Answer on Stackoverflow
Solution 10 - AndroidBernard IgiriView Answer on Stackoverflow
Solution 11 - AndroidManoharView Answer on Stackoverflow
Solution 12 - AndroidAhmed AwadView Answer on Stackoverflow
Solution 13 - AndroidNickView Answer on Stackoverflow