The first time you ship a mobile wallet feature, private keys feel like a tidy implementation detail. You generate them, store them somewhere “secure,” and move on.
Then a tester restores on a second device, and the app can’t sign in anymore. Or an Android built on a budget handset behaves differently from the flagship you developed on. Or you realize your crash reporter has been dutifully collecting far more context than anyone intended.
Private keys don’t usually get stolen because the crypto is weak. They leak because the key lifecycle wasn’t designed as carefully as the signing flow. The good news is that iOS and Android both give you strong primitives for protecting key material. The hard part is using them in a way that survives jailbreak/root conditions and the kinds of “helpful” platform behaviors that quietly copy data around.
Start with a threat model that fits mobile reality
If “store keys securely” is a requirement, it’s tempting to treat it like a single API call. On mobile, the risk is shaped by what attackers can realistically do to the device, what your app accidentally exposes, and what you’re willing to trade for usability.
What you’re defending against in practice
Most teams implicitly defend against “someone steals the phone.” That’s a good baseline, but it’s rarely the only one. Mobile threats are messy because the device is both a personal computer and a sensor platform. Data can leak through system services, debugging tools, and third-party SDKs, even when your own code is careful.
A grounded way to think about attackers is to separate three situations: a lost device that’s locked, a device with malware running in user space, and a device that’s rooted or jailbroken with deep inspection capabilities. Hardware-backed key storage helps most in the latter two because it aims to keep key material non-exportable and restrict when it can be used. Android’s Keystore is explicitly designed so keys can remain in the container and be used for crypto operations without exposing raw key bytes.
What “secure storage” means for a private key
On paper, the safest private key is one you never store. On a phone, that usually isn’t realistic because the app needs to sign when the user asks, sometimes offline, sometimes repeatedly. So “secure storage” becomes a set of constraints:
You want the key material to be hard to extract, even if the app’s sandbox is compromised. You want a thief to be blocked by the device lock state. You often want user presence before signing, especially for high-value actions. And you want backups and migrations to behave the way you expect, not the way the OS thinks is convenient.
OWASP’s mobile security guidance treats this as both “securely storing sensitive data” and “preventing leakage,” which is a useful framing because it forces you to look beyond the storage API and into side channels like logs and screenshots.
iOS: Treat Keychain as the storage layer, and Secure Enclave as the enforcement layer
On iOS, most secure key strategies start with the Keychain. The Keychain is not a simple database with encryption sprinkled on top. Apple documents that Keychain items are encrypted and tied into device security hardware, with the Secure Enclave playing a role in protecting keys used by the Keychain.
The practical takeaway is that you can often meet strong security goals without inventing your own crypto container.
Choosing the right Keychain accessibility for private key material
Keychain “accessibility” controls when an item is available, and whether it migrates to other devices through backups or device transfer. For private keys that should never leave the device, the choice tends to land on a “ThisDeviceOnly” option. Apple’s documentation for kSecAttrAccessibleWhenUnlockedThisDeviceOnly describes it as a setting that keeps items from migrating to a new device and limits access based on the device being unlocked.
This is one of those decisions that changes product behavior. If you mark a key as device-only, a user can’t restore that same key onto a new phone from a backup. That might be exactly what you want if your key is meant to be ephemeral, session-scoped, or recoverable via another mechanism. It’s also a problem if the private key is the user’s only access to funds, and you promised “restore works from backup.”
So the iOS story usually splits into two patterns: keys that must be portable are derived or imported and then stored with carefully chosen accessibility, while keys that must be non-portable are generated on-device with device-only accessibility and a recovery path that doesn’t rely on iCloud migrating the key.
Using Secure Enclave keys when you want “non-exportable by design”
If the goal is to make extraction of the raw private key as difficult as the platform can make it, Secure Enclave-backed keys are the iOS-native answer. Apple describes the Secure Enclave as a hardware-based key manager isolated from the main processor, intended to add an extra layer of security.
When you generate a key pair backed by Secure Enclave, the app generally performs cryptographic operations through APIs, while the private key material stays protected by hardware. That design matters because it changes the nature of compromise. An attacker might still coerce the app into signing something, but “dump the private key bytes” becomes far harder than it would be with a key stored as raw data.
The human factor shows up immediately: if a key is non-exportable, you can’t “back it up” in the traditional sense. The recovery story must be planned up front. If you need seed-phrase portability, you generally don’t rely on Secure Enclave to hold the long-term secret. You might instead use Secure Enclave as a guard to protect a wrapping key that encrypts other sensitive material, or to enforce user presence for certain operations.
Binding key use to biometrics without creating a support nightmare
iOS lets you attach access control policies to Keychain items. Apple documents flags like biometryCurrentSet, which ties access to the currently enrolled biometric set, meaning changes in enrolled fingers or face can invalidate access.
This is powerful, and it’s also a design decision with consequences. If you bind signing keys to “current biometric set,” a user who re-enrolls biometrics can lose access to the key. That may be acceptable for a high-security enterprise app with a server-side recovery flow.
A practical approach is to treat biometrics as a gate for use rather than a guarantee for recovery. Use biometric gating for higher-risk actions, but ensure there’s an alternate authentication path that doesn’t silently strand users. The platform gives you the knobs. Your product has to decide which failures are survivable.
Android: Keystore is the anchor, but device variability is the tax you pay
Android’s Keystore is the canonical mechanism for storing keys, so they are harder to extract from the device. The official documentation emphasizes that keys can remain non-exportable while still being usable for crypto operations, and that you can restrict how and when the key is used.
This aligns well with how you want private keys to behave. The challenge is that Android is not one environment. The Keystore’s security properties depend on device hardware and OS implementation details.
Hardware-backed keys
On many modern Android devices, Keystore keys are generated and used inside secure hardware such as a Trusted Execution Environment or a secure element. Testing guidance often points out that hardware-backed implementations prevent easy retrieval of encryption keys, even on rooted devices, in many cases.
But you can’t assume hardware backing universally. Some devices fall back to software-backed implementations. Others support StrongBox, which is a discrete hardware-backed option with stricter requirements. Android’s own hardware security best practices describe StrongBox environments in terms of dedicated CPU, secure storage, and strong resistance expectations.
User authentication constraints
Android Keystore supports requiring user authentication before a key can be used. This is exactly the control you want when signing is high impact. It also creates behavior differences across OS versions and OEM skins, especially around biometric enrollment changes and lock screen settings.
Design-wise, it helps to treat “user auth required for key use” as a policy layer with explicit fallbacks. If the device can’t enforce it reliably, you decide whether to block the feature, reduce capability, or move the sensitive operation elsewhere. Avoid silently downgrading security. Silent downgrades are the kind of detail that shows up later as an incident report.
Attestation and integrity checks
Android offers key attestation mechanisms that can help you reason about whether a key is hardware-backed and generated in a trustworthy environment. In practice, attestation is best used as a risk signal that influences policy. It won’t make a compromised device “safe,” but it can help you detect that you’re no longer operating under your preferred assumptions.
If you do use attestation, keep the product story simple: you’re trying to protect the user, not punish them for owning an older device. Make sure your team is aligned on what happens when attestation isn’t available, because it will happen.
The leaks that bypass secure storage
Storing the key correctly is only half the job. The other half is making sure the key doesn’t escape before it even reaches storage, or after it’s retrieved for use. OWASP explicitly calls out “preventing leakage” as part of the storage category because leaks often come from platform features and developer tooling rather than from the Keychain or Keystore itself.
Memory, logs, and crash reports are the usual suspects
If your signing flow pulls a private key into application memory as raw bytes, it becomes eligible for everything memory is eligible for: accidental logging, debugging dumps, crash reports, and forensic extraction on a compromised device. This is one reason non-exportable keys are attractive. When the private key never leaves secure hardware, you reduce the risk surface of “the key existed in RAM as bytes.”
If you do handle raw key material, treat it like you would treat plaintext passwords. Avoid string conversions. Avoid debug prints. Avoid storing it in objects that might be serialized. Be careful with third-party SDKs that collect “context” during failures. None of these controls is glamorous, but they’re where real incidents begin.
UI surfaces can leak more than you think
Mobile OSes are built around sharing and convenience. That’s great until the user copies a seed or private key into the clipboard and another app reads it. Or the app shows a QR code, and it ends up in screenshots and photo backups. Or your app is visible in the app switcher preview, and the screen is captured at the wrong time.
You don’t have to make the app paranoid. You do need a deliberate policy for “sensitive screens” that blocks preview snapshots and discourages copying secrets. If your product includes any flow where the raw private key is displayed, treat it as a last resort and document why it exists. Most of the time, you can design around it.
Backups and device migration can surprise you
On iOS, Keychain accessibility choices determine migration behavior, and device-only options intentionally prevent items from moving to a new device. On Android, backup behavior depends on how you store data and how the OS handles it, and OEM variations can shift what “restore” means.
If your app uses self-custody keys, you should assume users will change phones. That doesn’t mean you must make private keys migrate invisibly. It means you must make the recovery story clear and consistent. A secure design that regularly causes unrecoverable loss is still a design failure, just a different kind.
Bringing iOS and Android together without building two different products
Cross-platform key storage isn’t about finding one abstraction that hides the differences. It’s about building a policy layer that expresses what you want, and then implementing the strongest platform-specific enforcement you can.
A useful approach is to define a small set of “security levels” that are product-facing, not OS-facing. For example, you might have a level for routine signing that requires only device unlock, and a level for high-risk signing that requires explicit user presence. On iOS, user presence might map to Keychain access control with biometric or passcode gates. On Android, it might map to Keystore user-auth constraints.
Then you accept that some devices won’t support your preferred enforcement. That’s not an edge case; it’s a market reality. Decide what happens on weaker devices in a way that protects users without making the app unusable. Sometimes the right answer is to reduce convenience. Sometimes it’s to reduce scope. Sometimes it’s to offer an alternate signing path that keeps long-term keys off the device.
If you’re aiming for credibility, document these decisions internally as “invariants” and test them the way you test payments. Secure storage isn’t a feature you can visually verify. It’s a behavior you prove through repeatable scenarios.
Conclusion
Secure private key storage on mobile is less about picking the “best” API and more about designing a lifecycle that the platform can enforce. iOS gives you Keychain with strong protection modes and the option to lean on Secure Enclave for hardware-backed isolation. Android gives you Keystore with non-exportable keys and policy controls, but forces you to account for hardware and OEM variability.
When teams get this right, it’s rarely because they found a clever trick. It’s because they made a few hard decisions early, tested migration and failure paths as seriously as signing, and treated leakage prevention as part of storage rather than an optional “hardening pass.”




