Secure Key Storage in Secure Enclave for iOS Crypto Wallet
A typical crypto wallet problem on iOS: the private key stored in Keychain can be stolen if the device is compromised or jailbroken. Secure Enclave (SE) is a hardware-isolated processor inside the Apple SoC, guaranteeing that the key never leaves the chip. We offer SE integration for maximum protection. Our experience spans years in iOS development and cryptography. We implement everything turnkey: from design to testing on real devices.
Apple official documentation: Secure Enclave is a hardware security module isolated from the main processor.
How Secure Enclave Protects Private Keys
SE generates and stores keys inside its chip. The signing operation is performed inside SE; only the result is returned to the outside. Even your code does not have direct access to the private key. Combined with kSecAttrAccessControl and the .biometryCurrentSet flag, the key is automatically invalidated when the user's biometrics change. This means that when a new fingerprint or Face ID is added, the old key becomes inaccessible — a new one must be created. For a crypto wallet, this behavior is mandatory.
Limitations to Know Before You Start
Secure Enclave only supports P-256 (secp256r1, also known as NIST P-256). It does not support secp256k1, which is used by Bitcoin and Ethereum. Therefore, SE is not suitable for directly storing ETH/BTC private keys. A typical use for a crypto wallet is to store in SE the encryption key that encrypts the secp256k1 private key in Keychain. Alternatively, use SE for biometric protection of the Keychain entry via SecAccessControlCreateWithFlags.
If your app works with blockchains that use P-256 (e.g., some enterprise chains or NEAR protocol via ed25519), SE can be used for direct storage and signing.
Why SE Is Not Suitable for Bitcoin and Ethereum
Bitcoin and Ethereum use the secp256k1 algorithm, which SE does not support. The solution is an encryption scheme: generate an ephemeral P-256 key in SE, generate a secp256k1 key in memory, encrypt it with the SE public key via ECIES, and store the encrypted blob in Keychain. When signing, decrypt via SE (with biometrics), use the secp256k1 key for signing, and immediately zero out memory. This is safer than plain Keychain, as the key is never stored on disk in the clear.
Comparison of Storage Methods
| Method | Security | Performance | Applicability |
|---|---|---|---|
| Keychain (without SE) | Medium (depends on device passcode) | High | Any keys |
| Secure Enclave (P-256) | Maximum (hardware isolation) | Medium (requires biometrics) | Only P-256 |
| SE + encryption (for secp256k1) | Very high (key encrypted) | Lower (two crypto steps) | Any, but more complex |
Secure Enclave is orders of magnitude safer than software storage: the key is physically isolated, eliminating theft via memory analysis or disk dump. Even if the device is compromised, an attacker cannot extract the private key without biometric authentication.
Implementation of Signing and Storage
Creating a Key in Secure Enclave
let accessControl = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage, .biometryCurrentSet], nil )! let attributes: [String: Any] = [ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256, kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave, kSecPrivateKeyAttrs as String: [ kSecAttrIsPermanent as String: true, kSecAttrApplicationLabel as String: "wallet-signing-key-v1", kSecAttrAccessControl as String: accessControl ] ] var error: Unmanaged<CFError>? guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { throw error!.takeRetainedValue() } kSecAttrTokenIDSecureEnclave is the directive to create a key in SE. biometryCurrentSet invalidates the key when biometrics change (new fingerprint or Face ID added). For a wallet, this behavior is correct — explicit reauthentication is required.
Signing Data with an SE Key
let publicKey = SecKeyCopyPublicKey(privateKey)! let algorithm: SecKeyAlgorithm = .ecdsaSignatureMessageX962SHA256 guard SecKeyIsAlgorithmSupported(privateKey, .sign, algorithm) else { throw WalletError.algorithmNotSupported } var signError: Unmanaged<CFError>? guard let signature = SecKeyCreateSignature( privateKey, algorithm, dataToSign as CFData, &signError ) else { throw signError!.takeRetainedValue() } Signing is asynchronous from the UI perspective — while SE processes the request (and if biometrics are needed, while the user authenticates), the main thread is not blocked. The entire call should be placed in a Task or dispatch queue.
What’s Included in the Work
- Audit of current key storage scheme and threats.
- Design of the scheme with Secure Enclave (direct or with encryption).
- Implementation in Swift 5.9+ using Swift Concurrency.
- Integration with biometrics (Face ID / Touch ID) via
LocalAuthentication. - Testing on physical devices (all target models).
- Documentation for operation and key migration.
- Training the support and development team.
Work Process
| Stage | Duration | Result |
|---|---|---|
| Requirements audit | 1 day | Determine need for P-256 or encryption scheme |
| Design | 1-2 days | Architecture, prototype on real device |
| Implementation | 3-7 days | Swift code with async/await, biometric integration |
| Testing | 2-3 days | Scenarios: biometric change, reinstall, backup |
| Documentation and handover | 1 day | Documentation, team training |
How Long Does SE Integration Take?
Timelines range from 3 to 14 days depending on the complexity of the scheme. The simulator is sufficient for most development, but final testing must be done on a device.
Contact us for a consultation and assessment of your project — we will analyze your current scheme free of charge and propose the optimal solution. Get reliable protection for your crypto wallet today!







