The short answer {#short-answer}
A Secure Enclave SSH key is an SSH private key that is generated inside Apple’s Secure Enclave — a separate hardware security subsystem on iPhone, iPad, and Macs with a T2 or Apple Silicon chip — and that can never be read out of it. Not by the app that made it, not by iOS, not by you with a jailbreak and a debugger.
When you connect to a server, the SSH client never touches the private key. It hands the authentication challenge to the enclave, the enclave demands Face ID or Touch ID, and — if your face matches — it hands back a signature. That is what “Face ID SSH key” means when it is implemented properly, and it is genuinely a different security posture from a key file sitting encrypted on disk.
Two constraints fall out of that design, and most of this article is about living with them:
- The key is always
ecdsa-sha2-nistp256. The Secure Enclave only does NIST P-256. There is no such thing as an Ed25519 Secure Enclave key. - The key cannot be backed up, exported, synced, or moved. Lose the device, lose the key. Permanently.
Three things people call “Face ID SSH” {#three-tiers}
This is the distinction that matters most, because all three ship in real SSH apps and all three get marketed with the same Face ID icon.
| What Face ID gates | Where the private key lives | Can the key be extracted? | |
|---|---|---|---|
| 1. App lock | Opening the app | Wherever the app puts it | Depends entirely on tier 2/3 below |
| 2. Keychain key with biometric ACL | Reading the key out of the Keychain | Encrypted in the device Keychain, decryptable by the OS after auth | In principle yes — key material exists |
| 3. Secure Enclave key | Each individual signature | Inside the enclave, never anywhere else | No — by hardware design |
Tier 1 is a lock on the front door, not on the key. It is worth having — it stops the person who picks up your unlocked phone from opening a terminal — but it says nothing about how the key is stored. An app can have a beautiful Face ID lock screen and a private key sitting in plaintext behind it.
Tier 2 is what most mobile SSH clients actually do, and it’s a reasonable default. The key is stored in the iOS Keychain with an access-control policy, protected by hardware-derived device keys, marked non-exportable and non-syncing. Getting it out requires a device compromise, not just file access. It also lets you use Ed25519, which tier 3 cannot.
Tier 3 is the strong version. The private key is generated by the enclave and mathematically cannot leave it. The failure mode of tier 2 — someone with sufficient device access eventually gets the key bytes — does not exist here, because there are no key bytes outside the chip.
If you’re evaluating an app, the question to ask is not “does it support Face ID.” It’s “can I export this key?” If the answer is yes, it isn’t tier 3. Non-exportability is the whole feature.
How the signing actually works {#how-it-works}
Worth walking through once, because it explains both the strengths and the odd behaviours:
- At key creation, the app asks the enclave for a new P-256 key pair with an access-control policy attached — typically “user presence” (biometrics or passcode) or “biometry only.”
- The enclave generates the pair internally. It returns the public key and an opaque reference to the private key. The app stores that reference. It never sees private key material.
- You connect. The SSH server sends a public-key authentication challenge.
- The app builds the data to be signed and passes it, plus the key reference, to the enclave.
- The OS shows the Face ID prompt. This is a system dialog, not the app’s — the app cannot fake or skip it.
- On success, the enclave signs with SHA-256 over P-256 and returns a ~64-byte signature.
- The client sends that signature. The server verifies it against
authorized_keys. You’re in.
The practically important consequence of step 5: you get a biometric prompt per authentication, not per app launch. Reconnecting after a dropped connection means another Face ID. Opening a second session means another. Most people find that fine on a phone and irritating on a Mac doing git operations — which is exactly the trade you’re buying.
The server side of this is completely unremarkable. From sshd’s point of view, this is a plain ECDSA public-key authentication. It has no idea a security chip was involved, and it does not need to.
Why it’s always ECDSA P-256 {#p256}
Apple’s Secure Enclave exposes a deliberately tiny cryptographic surface. Via CryptoKit, SecureEnclave.P256 offers signing and key agreement on NIST P-256 (secp256r1) — and that’s the list. There is no P-384, no RSA, no Curve25519.
So:
| You want | Enclave-backed? | Notes |
|---|---|---|
ecdsa-sha2-nistp256 | Yes | The only real option. Accepted by default on mainstream OpenSSH servers |
ssh-ed25519 | No | Not supported by the enclave at any level. Keychain storage only (tier 2) |
ssh-rsa / rsa-sha2-* | No | Not supported by the enclave |
ecdsa-sha2-nistp384/521 | No | Enclave is P-256 only |
This is why every serious implementation converges on the same key type. Secretive, the macOS agent that popularised the idea, generates ecdsa-sha2-nistp256 exclusively for exactly this reason. Termius’s Biometric Keys feature on macOS/iOS/iPadOS is the same constraint. So is anything else that’s genuinely enclave-backed.
The practical read: if a mobile SSH app offers you “Ed25519 with Face ID,” it is not lying, but it is offering you tier 2. That may well be what you want — Ed25519 is a better curve story, and tier 2 is portable and backup-able. Just know which one you picked.
On the P-256-vs-Ed25519 question itself: P-256 is a mature, widely-implemented, FIPS-approved curve. The usual objections are about the provenance of its constants and how easy it is to implement badly in software — neither of which applies with much force to a signature performed inside a dedicated, audited hardware module. If you want the longer version of that argument, we cover it in ed25519 vs rsa.
Getting the public key onto a server {#setup}
The Face ID part is the app’s job. The server part is ordinary SSH:
- Generate the key in the app, choosing the Secure Enclave / hardware / biometric key option (naming varies by client). You’ll authenticate once to create it.
- Copy the public key. It looks like any other ECDSA line:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBB… iphone@termai - Append it to
~/.ssh/authorized_keyson the server, one key per line. Permissions matter:~/.sshshould be700,authorized_keys600, and the home directory must not be group-writable. - Connect and verify. Run with
-vfrom a desktop, or check the app’s connection log, and confirm you see the key type being offered and accepted. - Only then consider tightening the server — see SSH keys vs passwords for the
PasswordAuthentication nostep. Never disable password auth before a key login has succeeded from a session you can reopen.
Because the key can’t be exported, there is no ssh-copy-id path from the phone unless the app implements it. In most clients you’re copying the public key out via share sheet, clipboard, or an existing session. See creating SSH keys on iPhone for the mechanics.
When the server refuses it {#server-says-no}
ECDSA public-key auth landed in OpenSSH 5.7 (2011), so on any mainstream, default-configured server this just works. The failures are all configuration, and they cluster into four cases:
Hardened PubkeyAcceptedAlgorithms. Some hardening guides narrow the accepted list to Ed25519 and RSA-SHA2, dropping ECDSA entirely on curve-provenance grounds. Check what your server actually allows:
sudo sshd -T | grep -i pubkeyacceptedalgorithms
If ecdsa-sha2-nistp256 isn’t in that list, no enclave key will ever authenticate. Add it back in sshd_config (append rather than replace: PubkeyAcceptedAlgorithms +ecdsa-sha2-nistp256), reload sshd, and — as always — keep your existing session open while you test the change from a second one.
Confusing host keys with user keys. HostKeyAlgorithms governs the server’s identity, not yours. Tightening that has no bearing on whether your ECDSA client key is accepted. Two different knobs, similar names, regularly conflated.
Appliances and non-OpenSSH endpoints. Network gear, storage appliances, older managed hosts, and some Git servers ship narrow algorithm sets. This is where you find out that an unexportable key locked to one algorithm is less flexible than a file you can regenerate.
FIPS environments — the pleasant surprise. In FIPS-constrained estates, ECDSA P-256 is approved and Ed25519 has historically not been. A Secure Enclave key is often the easier key to get signed off there, not the harder one.
The part nobody plans for {#no-backup}
The key cannot leave the device. Follow that all the way through:
- It is not in your iCloud backup. Restore your iPhone from backup and the key is gone. The public key is still in
authorized_keyson twelve servers, matching a private key that no longer exists anywhere. - It does not sync to your iPad. Same account, same Apple ID, different enclave. Different key.
- Biometric re-enrollment can kill it. Access-control policies come in variants. A policy bound to the current enrolled biometric set is invalidated when that set changes — adding a fingerprint, re-registering your face. That’s the strictest and safest option, and it means an ordinary Face ID re-setup can destroy the key. A “user presence” policy (biometrics or passcode) survives re-enrollment but is weaker. Find out which one your app uses; it is rarely stated in the UI.
- Lost phone means lost access, unless you planned otherwise.
The working model that everyone converges on:
- One key per device. Phone, tablet, laptop each get their own enclave key, each enrolled separately in
authorized_keys. Name them in the key comment (iphone-15-termai) so the file is auditable. - Keep a break-glass key that is not enclave-bound — an Ed25519 key on an encrypted offline volume, or your VPS provider’s web console access. Test it once a year so you know it works before you need it at 3am.
- Revocation is now trivial and it’s the good news. Lost phone? Delete that one line from
authorized_keys. No key rotation across every device, no worrying about a copy already in someone’s hands — there was never a copy.
That last point is genuinely the strongest argument for this whole model. Per-device unexportable keys turn “my phone was stolen” from an incident into a one-line edit.
The Android equivalent {#android}
Android has the same idea under different names. Keys go into the Android Keystore, and KeyGenParameterSpec lets an app require user authentication before a key can be used — the direct analogue of the enclave’s access-control policy.
Two hardware tiers matter:
| Tier | Where the key lives | Availability |
|---|---|---|
| TEE-backed (“hardware-backed”) | Trusted Execution Environment on the main SoC | The common case on modern Android |
| StrongBox | A separate, dedicated secure element | Android 9+ (API 28) and only on devices that ship one — e.g. Pixel’s Titan chip |
StrongBox is the closest match to Apple’s Secure Enclave: physically separate silicon, its own CPU and storage. It supports EC P-256 for signing — and notably not P-384 — so the same “your key is P-256” conclusion lands here too, for the same reason.
The invalidation footgun exists here as well: keys can be configured to be invalidated when a new biometric is enrolled, and on many configurations that is the default. Same advice — assume a fingerprint change can destroy the key, and keep a break-glass path.
Where Android differs meaningfully from iOS: StrongBox presence varies enormously across the device landscape, so an app can’t assume it. Whether a given Android SSH client requests StrongBox, falls back to TEE, or just uses ordinary Keystore storage is not usually visible in its UI. For any specific client, treat that app’s own documentation as the authoritative answer.
Secure Enclave vs FIDO sk-keys {#vs-fido}
There is a second hardware-backed SSH option, and people conflate the two: OpenSSH’s FIDO/U2F key types, ecdsa-sk and ed25519-sk, added in OpenSSH 8.2. These delegate the private key to a FIDO authenticator — a YubiKey, or on newer clients a platform passkey. Blink Shell, for instance, documents WebAuthn/passkey support for SSH on iOS.
| Secure Enclave key | ed25519-sk / ecdsa-sk | |
|---|---|---|
| Key type | ecdsa-sha2-nistp256 only | Ed25519 or P-256 |
| Server requirement | None — plain ECDSA, works on ancient sshd | OpenSSH 8.2+ on the server |
| Hardware | The device you’re holding | Device platform authenticator or an external token |
| Portable between devices | No | With an external token, yes — carry it |
| Touch/presence per use | Yes (Face ID / Touch ID) | Yes (tap / biometric) |
The deciding factor is usually the server, not the client. sk key types need OpenSSH 8.2+ at both ends; a Secure Enclave key needs nothing new anywhere. If you administer a mixed estate with old boxes in it, the enclave key is the one that just works. If you want one credential that follows you across phones and laptops, a hardware token with ed25519-sk is the better shape.
Should you use one {#should-you}
Yes, if:
- The phone is a real admin device that reaches production, not a convenience.
- You want per-connection approval — a stolen unlocked phone still can’t authenticate.
- Your estate is mainstream OpenSSH (any recent Linux distro, any normal VPS).
- You’re comfortable with one key per device and can maintain
authorized_keysaccordingly.
Probably not, if:
- You need one key across phone, tablet and laptop. Get a hardware token instead.
- You connect to appliances or hardened hosts that reject ECDSA, and you don’t control their config.
- You have no break-glass access to your servers. Fix that first; then this is safe to adopt.
- Per-connection biometric prompts will genuinely wear you down — on a Mac with frequent git operations, that’s a real consideration.
For most people running a handful of servers from a phone, tier 2 — an Ed25519 key in the Keychain, non-exportable, non-syncing, behind a biometric app lock — is already a large improvement over a key file on a laptop, and it is portable. Tier 3 is the upgrade you make when the phone is a production credential and unexportability is worth losing backup for.
Where TermAI sits {#termai}
Straight, without marketing: TermAI generates or imports keys on-device and stores them in the iOS Keychain / Android Keystore, with Face ID gating access to the app. Private keys are not uploaded; if you enable cloud sync, it’s client-side encrypted. That is tier 2 in the table above, with a tier 1 app lock on top.
Whether TermAI currently exposes enclave-generated ecdsa-sha2-nistp256 keys as a separate key type — tier 3 — is a product question rather than a blog one; TermAI’s own documentation and the key options shown in the app at the time you read this are the authoritative answer.
What is true regardless: on a phone, key storage is only one of the things you’re trusting. The other is whatever executes commands. TermAI’s AI is assistant-style on purpose — it writes the command onto your input line and stops, so a human reads it before anything runs on a production box. A hardware-protected key and an autonomous agent are a strange pairing: you’d have carefully made the credential unstealable and then handed its use to a loop that doesn’t pause. Letting AI run shell commands on mobile, safely is the longer argument.
FAQ {#faq}
Free on iOS and Android. 5 AI requests/day on the free tier, plus unlimited SSH/SFTP and built-in Tailscale.