Recommendations to secure mobile API keys and secrets
In “We reverse engineered 16k apps, here’s what we found”, Fallible used their online reverse engineering tool to probe Android apps looking for secrets. They tested over 16,000 apps and found that roughly 2500 of them contained API keys and/or secrets. If their tool can find them, so can people looking to exploit your apps.
Let’s assume that you are developing an Android or iOS mobile app. Your backend app server nicely exposes information through a RESTful API. To authenticate your mobile app, an API key is passed with every API call. This key, simply formatted as <id>:<secret>, enables the backend server to easily identify your app and validate the shared secret. Your mobile app also directly calls a few 3rd party APIs, which each use a similar key strategy.
We’ll assume you use TLS with certificate pinning to try to prevent your API calls from being observed, but your API keys are all stored as constants, XXX_API_KEY, in your mobile app package. If any of your API keys can be observed or found inside your app, you are compromised.
Here are some recommendations:
1. Minimize # Secrets in Your App
Do we really need all the secrets you have stored in your app? A surprising number of keys are for 3rd party APIs we thought we might use but never did. Scrub your app of all these secrets.
For the 3rd party API keys we’re actually using, consider setting up proxy servers for all these services.
Instead of calling a 3rd party API directly, call it indirectly through a proxy, using your primary app API key as authentication. With this scheme, you can remove all 3rd party API keys from your mobile app; they are infinitely more secure hidden behind your proxy service. Additionally, if you need to change a 3rd party API key for any reason, you can do so without requiring an update of your mobile app’s installed base.
We’ve just reduced our key requirements down to a single API key. If that app key is compromised, a malicious actor can still exercise the 3rd party APIs through the proxies. On the proxy, you can add some additional protection, for example, allowing only certain sequences of API calls, to try to limit the damage. If your app key is compromised, you will need to change it and roll out a mobile app update, but at least you will not need to change the 3rd party API keys as well.
2. Obfuscate Remaining Secrets
As it stands, a quick search of the strings in your app will easily find your remaining APP_API_KEY. Use code obfuscators to make it harder to locate and extract the secret constant. Consider further encoding the API key in some computationally simple way and cutting up and distributing the encoded secret throughout your app. Reassemble and decode the secret in memory only as needed.
Your key may eventually be compromised, but make it as difficult to reverse engineer as you can. Unlike with the proxied API keys, if this app key is stolen, you will have to upgrade your entire installed base to replace it.
3. Send Tokens, not Keys
Even though we use TLS, an attacker may still find ways to observe our API calls. Instead of sending the full <id>:<secret> with each call, let’s use the <secret> to encrypt or sign all or part of the API communication.
A simple way to do this is to have the mobile app use the <secret> to encode a JSON Web Token (JWT) with a payload containing our <id> and a short expiration time.
With each API call, a token is sent in place of the API key; the API secret is not exposed. Using the same <secret> , the backend servers can validate the token’s encoding and lifetime. The mobile app generates a new token whenever the previous one expires.
If a token is stolen, it can be used to call your API and proxies, but it will only succeed for the lifetime of that token, so keep the expiration time short.
Of course, if the obfuscated secret is compromised, an attacker can generate valid tokens, and you still need to upgrade your installed base to fix it.
You can use the <secret> in different ways to achieve other benefits. For example, the Amazon S3 REST API signing and authorization scheme uses the <secret> to provide additional protection against message-tampering.
4. Remove the Remaining Secret From App
Can we remove the last remaining API key from the app? We could delegate token generation to an off-device service, similarly to how we delegated 3rd party API calls to a proxy service. However, for this to work, our service must have a means of recognizing our authentic, untampered app while rejecting all others.
Our app authentication service will use the unique characteristics of our mobile app to attest the app’s integrity and authenticity. The service’s dynamic authentication challenges will be much more resilient than an obfuscated static secret approach. If satisfied, the authentication service returns a valid, time-limited API token.
Now not only is your API secret not exposed by an API call, but your app’s API key is no longer exposed anywhere inside your mobile app. Since all API keys are secured behind servers, any API key can updated without requiring any updates or loss of service to your mobile app.
Do run your apps through Fallible’s online tool; they’ve provided a nice service to the developer community. Heads up though, even if you pass, it doesn’t mean your API is completely secure, but you’re on the way.
I think that removing that last API key from your mobile app is a major boost for your API security, but, full disclosure, I work on a team offering a reliable mobile app authentication service. If you’re interested, you can learn more about it at approov.io; I’d love your feedback.
Thanks for reading! I’d really appreciate it if you recommend this post (by clicking the ❤ button) so other people can find it.
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!