Categories
Programming Security

#wedskaas: What every developer should know about app security, part 1: How to handle API keys

A few weeks ago at Xamarin Dev Days Graz I was asked a question I’ve been asked many times before: How do I obfuscate my mobile app’s code? My answer is always a counter-question: What are you trying to achieve by obfuscating your code? I’ve gotten different answers to this question and I want to dig into some answers I’ve received in the past in this blog post series.

A common answer is that there is some key or certificate that needs to be protected in the code.

I don’t believe code obfuscation is a good solution to this problem. I believe there typically isn’t anything that requires obfuscation inside an app’s code. Any attempts at hiding things inside an app’s code will only increase the effort it takes to get at that information but will not prevent it.

Some solutions I’ve heard for hiding a key inside the app code:

  • Encrypt the key (using which key?)
  • Assemble the key from strings in different portions of your source code
  • Hide the key in the last bytes of an embedded image
  • Use a code obfuscation tool
  • Don’t put it into the app’s code at all but load the key from a backend after the app has been installed

Let’s look at API keys

Let’s look at why these keys are there in the first place. In most cases, the key is used to authenticate the app towards a backend which is commonly referred to as an API key. For example, if you look at the Parse SDK Getting Started in the Xamarin Component Store, you’ll find this:

What the API expects here is a unique app ID and a key. The key gives you access to your data on the backend. The big problem here is that this one key is being used by all instances of the app.

A team at the University of Paderborn did an experiment to see if they could find that key inside app bundles in the Google Play store for apps that were using Parse. Using a simple static code analysis was enough for some apps but combining that with a dynamic (runtime) analysis was enough to retrieve the API key for all apps that were analyzed. Think about it: At some point, the app will have to call the method ParseClient.Initialize() with the plaintext key in memory.

The team then went on to analyze what they could access using those extracted API keys. The least information they could get for each app was a list of verified user email addresses. In many cases, they had complete read or even write access to all database tables for each app’s backend. The data found ranged from pictures, location information, contact information, and sales information to data gathered by trojans (yes, even the bad guys used Parse as a backend).

Parse is just an example here. You can find these API keys used in many backends and there is hardly an app out there that does not use any API keys. Anyone who extracts that API key can do whatever is allowed with that API key. This may or may not be a problem depending on what type of API key is at hand. It’s important to put yourself into the position of an attacker: What could someone who gets ahold of your API key do with that key?

In the case of Parse, it would have been possible to add user management and additionally authenticate users to access resources. That way, the API key itself would not have allowed access to all this data.

Authenticate the user, not the app

If you’re designing your own backend, you need to think about what type of data it is you are exchanging with your backend. Sometimes it may be OK to have your app access data from the backend without providing any credentials. This may be true for information you could also put on an unauthenticated website. But many times, you will be exchanging non-public information with your backend. In this case, it is always better to authenticate not the app but each user. The advantages are:

  • You can fully control what each user can see, even after your app has been distributed.
  • You can revoke access for individual users without breaking access for all other users.

If an API requires an API key, call it from your backend

If you do have to use a service that requires an API key, it is usually best to not make that call from within your app. A better approach is to make an authenticated user request the information from your own backend and then let your backend make the 3rd party API call on behalf of the user. The advantages here are:

  • It is not possible for an attacker to extract the API key and use your account to access the 3rd party API.
  • You can control how many calls are made with your API key.
  • You can cache results from the API and reduce the number of calls necessary.
  • You can block individual users from using the 3rd party API.

To hear more about this topic, you can watch my Xamarin Evolve talk on app security or stay tuned on this channel for more posts in this blog series.

Other post in this series

3 replies on “#wedskaas: What every developer should know about app security, part 1: How to handle API keys”

Very interesting article Kerry! It makes sense to make the init calls from the backend. However, there are some services that must be initiated from the mobile app (for instance: crash logging services like HockeyApp). What would you recommend in that case?

Right, sometimes you don’t have a choice. If the worst that could happen is that somebody could add additional traffic to your HockeyApp stats if they manage to extract the API key, then that would probably be OK. If the worst that could happen is that the attacker gets read/write access to the data of all your users, it is time to rethink your app’s security, even if that means ditching a backend-as-a-service provider or routing it through your own backend.

Couldn’t agree more. This is something I still have to educate developers on. I always have the thought, that your Mobile Code is completely public once released on the store. There is nothing you can hide. Always use user entered data, to retrieve tokens. Never have your code contain any keys.

Leave a Reply

Your email address will not be published. Required fields are marked *