A mobile security team should evaluate secrets and key protection in three passes: an inventory of every secret, key and sensitive file the app carries, with its storage boundary; a written lifecycle that assigns generation, access, rotation and recovery to named owners; and a runtime test of what happens when plaintext exists on a hostile device. This guide covers Android and iOS apps, including Flutter and React Native. Keep on your server whatever can stay there. For what must ship, Otukan protects the stored value, the key that guards it and the moment your code uses it.
Background on the control itself is covered on mobile app secrets protection. Other evaluation guides are on solution guides.
Data inventory
Start with a list that names things precisely. “API keys” is not an inventory entry; “the payments endpoint token in the release configuration file” is.
What the app carries
- Which values are compiled into the build: API tokens, client identifiers, endpoint addresses, feature flags, licence strings?
- Which files ship in the package: configuration files, certificates, machine learning models, bundled scripts?
- Which values arrive after install: session tokens, refresh tokens, cached account data, downloaded content keys?
- Which of these could move to your server and be fetched only by a verified app?
Where each item lives
| Item type | Android storage boundary | iOS storage boundary | Question to ask |
|---|---|---|---|
| Compiled constant | DEX string pool or native library | Mach-O string sections | Can it leave the build entirely? |
| Bundled file | APK or App Bundle assets and resources | App bundle beside the executable | Is it protected in the package? |
| Session credential | App-private storage, Keystore-wrapped key | Keychain item with an access class | Is it bound to this install? |
| Private key | Android Keystore, hardware-backed where the device offers it | Keychain, or Secure Enclave on supported devices | Can it be exported at all? |
Framework apps need an extra line: Flutter compiles Dart into a native library, and React Native ships a JavaScript or Hermes bytecode bundle. Secrets in either are as readable as native code unless protected.
How Otukan answers. Otukan protects the values your app has to ship. String protection keeps endpoints and lookup keys in protected form until the code needs them, asset protection covers configuration files and bundled scripts, and the code that fetches and assembles secrets is obfuscated. Each release comes with a list of protected strings, so your inventory and your build stay in step.
Key lifecycle
A key without an owner is a key nobody rotates. Write the lifecycle down before you test anything.
Responsibilities to assign
- Generation: is each key created on the device, on your server or at build time, and by which process?
- Access: which code paths may use the key, and does use require user presence or device unlock?
- Rotation: how is a key replaced, and what happens to builds already in users’ hands?
- Revocation: how do you cut off a key after a leak without breaking honest users?
- Recovery: what does a user go through after reinstalling the app or moving to a new phone?
Platform facts to confirm
- On Android, are keys created in the Android Keystore, and is hardware backing checked rather than assumed?
- On iOS, which Keychain access class does each item use, and is it limited to this device?
- On iOS, which keys belong in the Secure Enclave? It generates and uses supported key types inside the hardware; it is not a store for bulk data.
- Who in your organisation holds signing keys, and do they stay out of every third-party process?
How Otukan answers. Otukan uses the platform keystore on each operating system: the Android Keystore on Android and the Keychain on iOS. On devices with a Secure Enclave, Otukan protects its data key with a Secure Enclave key that is generated and used inside the hardware. On Android, rotation re-wraps data keys under a new Keystore key. Rotated embedded secrets ship in the next protected build through your existing pipeline, and your signing keys stay with your team.
Runtime scope
Every secret becomes plaintext at the moment of use. That moment is where evaluations are won or lost.
When plaintext exists
- Which secrets are decoded in app memory, and for how long?
- Which keys never leave the keystore, so the app only receives the result of an operation?
- Which flows send a decoded value over the network, and is that channel protected?
What encryption does not prevent
- A hooking framework reading a value after decryption.
- A debugger pausing the app while a token sits in memory.
- A repackaged copy calling your own decryption code.
- A rooted or jailbroken device giving another process access to app storage.
How Otukan answers. Otukan guards the moment of use. It detects instrumentation attached to the functions that handle keys, signatures and tokens, and can stop that call under your policy. It also detects debuggers and emulators, rooted Android devices and jailbroken iOS devices with separate checks, and repackaged copies. You set the response per flow, from observe to terminate, and your backend receives a signal when key-handling code is hooked or protected resources are tampered with. With app attestation, your backend can issue short-lived credentials to the genuine app, so the build carries fewer long-lived secrets.
Tests to run on your own build
| Test | Expected evidence |
|---|---|
| Search the protected build for known secret values | Values absent in readable form |
| Attach a hooking framework at the decryption point | Detection fires and your policy applies to the call |
| Run the app on a rooted or jailbroken test device | Key use follows your policy |
| Install a re-signed copy | Integrity check reacts |
| Reinstall and restore | Recovery works as documented |
If your app is already published, the free App Exposure Check is a passive review of your public store release, and its findings give your inventory an outside view. Plaintext lifetime in memory is covered on the memory protection solution guide.