← Blog ·

E-commerce Apps: Your Payment Gateway API Key Shouldn't Ship Inside the APK

Download the APK of an e-commerce app, run it through apktool or jadx, and search for the string sk_live_. In a surprising number of cases, it's right there. The secret key for Stripe, for the local payment gateway, whatever it is — sitting inside a binary that anyone can install on their phone and take apart in a few minutes.

You don't need a sophisticated attacker for this. A strings pass over the decompiled APK and a grep is enough to surface credentials that should never have left the backend in the first place. And in e-commerce apps the impact isn't theoretical: depending on the key's scope, that credential can create charges, issue refunds, or read transaction data.

This post is for two different people who run into the same problem: the indie dev building this app right now, deciding where the key lives, and the enterprise team that already has the app in production and just found out — or worries they will — that the key is hardcoded.

Why the key ends up in the APK

It's rarely pure negligence. It's the path of least resistance:

  • The team needs the app to talk to the payment gateway, and the fastest way is dropping the API key into a build-time environment variable, which Expo or React Native bundle as-is into the app.
  • An AI assistant generates the gateway integration code and puts the key directly into a config file, because that's what the sample docs show.
  • Nobody greps the final APK or IPA for sensitive strings before shipping it to the store.

In the AI-generated code era this has gotten more common, not less: the assistant solves for "it works," and rarely stops to ask where a secret should actually live. We cover the full picture of why this is the #1 exposure vector today in /ai-security.

The correct pattern: the secret doesn't live in the binary

The fix isn't obfuscating the key or moving it into a .env file that still gets bundled anyway. The fix is that the binary never contains the secret in plaintext — not even encrypted with a key that also ships in the APK, which just adds one extra step to the same problem.

The pattern Koove supports today: the secret is stored encrypted server-side (the server never sees plaintext, only ciphertext), and it's decrypted only on devices that pass hardware attestation — App Attest on iPhone, Play Integrity on Android — plus the user's biometrics. No attested device, no decryption.

If you're building the app right now

You store the key once, from your machine or your CI pipeline, using the CLI:

koove set STRIPE_SECRET_KEY sk_live_51abc... --env prod
koove list --env prod

In the app's code — whether you write it yourself or an AI assistant generates it — there's no literal key anywhere anymore. Just a name:

import { KooveClient } from '@koove/sdk';

const koove = new KooveClient({
  apiUrl: 'https://api.koove.io',
  appId: process.env.KOOVE_APP_ID,
  appToken: process.env.KOOVE_APP_TOKEN,
});

await koove.init(); // attestation (App Attest / Play Integrity) + registration + biometrics

// your backend hands the app the encrypted envelope for STRIPE_SECRET_KEY
const stripeKey = await koove.decryptSecret(envelope);

stripeKey only ever exists in device memory, after the SDK has verified attestation and biometrics. It's never compiled into the APK or the IPA. This requires a dev build of Expo/React Native (it won't work in Expo Go) — full setup is in /docs/sdk.

If you're already in production

If your enterprise team already has a hardcoded key and just found out — via an audit, a pentest, or because someone found it in a leaked APK — migrating the code going forward isn't enough on its own. You have to treat the current key as potentially compromised.

The realistic process:

  1. Rotate the key in your payment gateway's dashboard (Stripe, whichever provider) — that part is on the provider, not on Koove.
  2. Store the new key in Koove: koove set STRIPE_SECRET_KEY sk_live_new... --env prod.
  3. Remove every literal reference from the codebase and replace it with decryptSecret.
  4. Ship a new build. Older app versions with the hardcoded key are still out there on devices that haven't updated — that can't be undone.
  5. Manage which devices can decrypt going forward with koove devices, koove device-approve, and koove device-revoke according to your access policy.

What Koove can't do — said plainly

If an attested device with valid biometrics already decrypted the key, and someone extracted that value at runtime — say, via a hook on a device that got past attestation through some other means, or simply because the legitimate device owner did something improper with the value once it was in memory — revoking that device afterward doesn't undo the extraction. device-revoke cuts off future access; it doesn't erase what already left.

This also isn't forward-secret, Signal-grade cryptography: it's an envelope scheme using X25519 + AES-256-GCM and HKDF-SHA256, built on open primitives in @koove/crypto, not a ratcheting messaging layer. What it does prevent — verified end-to-end on physical hardware on both platforms — is the far more common failure: a plaintext key sitting inside a binary that anyone can decompile without ever touching a real user's device. The full architecture is documented at /security.

Where to start

If you're building the app: wire in the CLI and SDK from the first sprint, before the gateway key ever touches your repo.

If you're already in production: treat this as an active security incident for as long as the key sits inside a published APK, and prioritize rotation and migration accordingly.

Either way, check the plans at /plans and create your account at /register to start moving your e-commerce app's secrets out of the binary.

AI-generated code

Your AI writes the code. Who guards the secrets?

The most common security failure in AI-generated apps is exposed credentials. With Koove, your own assistant stores every token from the CLI — encrypted on your machine, never in the code or the repo.