Android App Security · Storing Data Safely · Lesson 4 of 5
Secrets in the Code
Learn why anything shipped inside an app is public, and where real secrets belong.
Anything in the APK is public
Developers sometimes need a password or key so their app can talk to an online service. The tempting shortcut is to type it straight into the code, or into a resource file like strings.xml. That's called a hardcoded secret.
The problem is that the app isn't private. You saw in Inside an APK that an APK is a ZIP archive anyone can download and open. Its code in classes.dex, its resources and its asset files all travel to every phone that installs it. Google's own security guidance says an attacker with reverse engineering tools can recover a hardcoded secret very easily. Reverse engineering means working backward from a finished program to understand how it works, and there are free, widely used tools for doing it with Android apps.
Many developers count on R8, the tool that shrinks and optimizes Android apps when they're built. One of its jobs is obfuscation: it renames classes and methods to short, meaningless names, so com.example.MyActivity might become a.b.a. Google describes this as a way to make the app smaller. It makes the code harder to read, but it isn't encryption, which scrambles data so it's unreadable without a key. The text of a password still has to be in the app for the app to use it, whatever the surrounding code is called.
What if the developer encrypts the secret inside the app? Then the app also has to carry the key to decrypt it, or rebuild that key somehow. Anyone patient can follow the same steps the app does.
The safe assumption is simple. Once a secret ships inside an app, treat it as public. If that's already happened, the fix isn't hiding it better in the next version. It's canceling that key with the service and issuing a new one, kept somewhere safer.
What belongs on a server
If secrets can't live in the app, where do they go? The usual answer is the developer's own server, a computer the developer controls and users never get to unpack.
Here's how it works for a shopping app. The secret key for the payment provider stays on the server. The app signs you in, and the server hands the app a short-lived token, a temporary pass that works only for your account. When you tap Pay, the app asks the server, and the server makes the real call to the payment provider with its secret key. If someone copies the app, they get no key, and a stolen token only covers one account and expires soon.
Things that belong on a server include database passwords, payment and admin keys, and any key that lets its holder act for every user or read everyone's data.
Not every key is a real secret, though. Some services hand out keys that are meant to sit inside apps, like a Google Maps API key, which identifies which app is using the map. Google expects it to be visible, so the protection is a restriction set in the developer's Google Cloud account:
- An application restriction says the key works only from an Android app with a specific package name and signing certificate fingerprint.
- An API restriction says the key can call only the services it needs, such as the Maps SDK for Android.
A restricted key copied out of the app is far less useful to someone else's app, though restrictions reduce misuse rather than make a key secret.
Secrets created on the phone itself, like a key that encrypts your local notes, fit the Keystore you learned about in module 2. The rule of thumb: public identifiers can ship with restrictions, per-user secrets go in the Keystore, and master secrets stay on the server.
Can you answer these?
- A developer hardcodes a payment service password, then turns on R8. The class holding it is now named a.b.c. What about the password?
- Which of these must never be shipped inside an app?
Answer them in the app to earn 70 XP (Pro).
Answer in the app