Extracting Secrets from an APK: What an Attacker Sees Inside Your Binary
An Android APK is not a locked box. It's a ZIP file with a .apk extension, and anyone with unzip and ten minutes can look inside. If your app ships an API key, a signing secret, or a backend token baked into the code, decompiling the APK is often all it takes to get it.
This isn't a theoretical attack. Public APKs get pulled apart constantly — by security researchers, competitors, and bots that scrape app stores looking for exposed credentials. If your CI pipeline or your AI coding assistant ever wrote a secret directly into source, it's probably still there in every release you've shipped.
Your APK is just a ZIP file
Try it yourself:
unzip app-release.apk -d app_unzipped
You'll get classes.dex, resources.arsc, AndroidManifest.xml, an assets/ folder, and any native .so libraries. None of that is encrypted by default. Release signing protects integrity, not confidentiality.
Decompiling in minutes: the attacker's toolkit
grep first, decompile later
Before touching a proper decompiler, most attackers just grep for patterns:
strings app-release.apk | grep -Ei "sk_live_|AIza|xox[baprs]-|secret|apikey|token"
strings alone finds a surprising number of hardcoded keys — Stripe live keys, Google API keys, Slack tokens — sitting in plaintext inside resources.arsc or assets/.
Full decompilation
For actual source recovery:
jadx -d app_src app-release.apk
or, for resources and the manifest:
apktool d app-release.apk -o app_decoded
grep -r -i "api_key\|secret\|token" app_decoded/res/
jadx reconstructs readable Java/Kotlin from the DEX bytecode. It won't perfectly reverse minified code, but renaming variables doesn't hide string literals — a secret assigned to a variable called a is still the same secret.
Native libraries aren't safe either
If you moved a key into a native .so file thinking it would be harder to reach, the obstacle is smaller than it looks:
strings libnative.so | grep -i "key\|secret"
Static strings in native code are just as visible; you've only added a disassembly step, not real protection.
What attackers actually find
In practice, decompiled APKs commonly expose:
- Hardcoded third-party API keys (payment providers, maps, analytics, AI providers)
- Backend URLs and static bearer tokens meant for "internal" APIs
- Firebase configs with overly permissive rules
- OAuth client secrets that should never live client-side
- Signing or encryption keys copy-pasted from a
.envfile intoBuildConfig
That last one is increasingly common. AI coding assistants are fast at wiring up an SDK, and the fastest path to "it works" is often pasting the key straight into the code. We wrote about this pattern in more detail in AI-era security — the assistant has no idea that key is about to ship inside a public binary.
Why obfuscation doesn't fix this
ProGuard/R8 renames classes and methods; it doesn't encrypt string literals unless you explicitly add string-encryption tooling, and even then a determined attacker can hook the app at runtime (Frida, Xposed) and dump the decrypted value the moment your code uses it. Obfuscation raises the cost of a full source-level clone; it doesn't protect a static secret compiled into the binary. Treat it as a deterrent, not a defense.
The real fix: never ship the secret
The only durable fix is architectural: the secret should never exist inside the APK, encrypted or not. If it's not there, there's nothing to decompile.
That means your app fetches secrets at runtime, from a service that:
- Never has access to the plaintext itself (zero-knowledge server).
- Only releases secrets to a client it has verified is a genuine, untampered install of your app.
- Adds a human gate — biometrics — before decryption happens on-device.
This is the model Koove is built around. Instead of STRIPE_KEY=sk_live_... sitting in your bundle, your code and your AI assistant reference the secret by name, and the actual value is stored server-side as ciphertext the server can never read.
How Koove removes secrets from the binary
On the developer side, secrets are pushed via the CLI, never committed to a repo or bundled into a build:
koove set STRIPE_SECRET_KEY sk_live_xxx --env prod
On the client side, the mobile SDK requests the secret only after proving the device is real:
import { KooveClient } from '@koove/sdk';
const koove = new KooveClient({
apiUrl: 'https://api.koove.io',
appId: 'app_xxxx',
appToken: 'xxxx',
});
await koove.init(); // Apple App Attest / Play Integrity, then registration
const stripeKey = await koove.decryptSecret(envelope); // gated by biometrics, decrypted on-device
Under the hood, secrets are encrypted with an X25519 + AES-256-GCM envelope using HKDF-SHA256 key derivation, built on open-source primitives (@koove/crypto) — you can read the code instead of trusting a claim. Attestation is checked against real hardware on both platforms, not a simulator or a dev bypass, which is exactly what matters for this threat model: even if an attacker fully decompiles your app, cloned or repackaged copies fail attestation and get no secret. Full details live in the security and trust center.
The honest limits
Two things worth saying plainly. First, Koove's envelope encryption has no forward secrecy — it's not a Signal-style ratchet, and we don't market it as one. Second, once a secret has been decrypted on a legitimate, attested device, it lives in that app's memory like any other runtime value; no secrets manager can retroactively "unsend" a value that already reached a device's RAM. What Koove does solve is the far more common failure: a static secret sitting in a public binary, waiting to be grepped out by anyone who downloads your APK.
For the full CLI and SDK reference, check the docs; pricing is on /plans, and common questions are answered in the FAQ.
Wrap-up
Decompiling an APK is not a sophisticated attack — it's unzip, jadx, and grep, and it takes minutes. The only reliable defense is making sure there's nothing worth finding: no hardcoded keys, no tokens in assets/, no secrets living anywhere near your source.
If your app or your AI assistant has ever put a real key directly into code, it's worth checking your last shipped APK today. Sign up and move those secrets out of the binary before someone else finds them for you.