Skip to content

Instantly share code, notes, and snippets.

@arianvp
Last active December 4, 2025 22:49
Show Gist options
  • Select an option

  • Save arianvp/5f59f1783e3eaf1a2d4cd8e952bb4acf to your computer and use it in GitHub Desktop.

Select an option

Save arianvp/5f59f1783e3eaf1a2d4cd8e952bb4acf to your computer and use it in GitHub Desktop.
Native Secure Enclaved backed ssh keys on MacOS

Native Secure Enclave backed ssh keys on MacOS

It turns out that MacOS Tahoe can generate and use secure-enclave backed SSH keys! This replaces projects like https://github.com/maxgoedjen/secretive

There is a shared library /usr/lib/ssh-keychain.dylib that traditionally has been used to add smartcard support to ssh by implementing PKCS11Provider interface. However since recently it also implements SecurityKeyProivder which supports loading keys directly from the secure enclave! SecurityKeyProvider is what is normally used to talk to FIDO2 devices (e.g. libfido2 can be used to talk to your Yubikey). However you can now use it to talk to your Secure Enclave instead!

recording.mov

Seems this method was first discovered in https://lists.mindrot.org/pipermail/openssh-unix-dev/2024-July/041451.html

Key setup

See man sc_auth and man ssh-keychain for all the options

To create a Secure Enclave backed key that requires biometrics, run the following command and press TouchID:

% sc_auth create-ctk-identity -l ssh -k p-256-ne -t bio

You can confirm that the key was create with the list-ctk-identities command:

arian@Mac ssh-keychain % sc_auth  list-ctk-identities       
Key Type Public Key Hash                          Prot Label Common Name Email Address Valid To        Valid 
p-256-ne A71277F0BC5825A7B3576D014F31282A866EF3BC bio  ssh   ssh                       23.11.26, 17:09 YES

It also supports listing the ssh key fingerprints instead:

% sc_auth  list-ctk-identities -t ssh
Key Type Public Key Hash                                    Prot Label Common Name Email Address Valid To        Valid 
p-256-ne SHA256:vs4ByYo+T9M3V8iiDYONMSvx2k5Fj2ujVBWt1j6yzis bio  ssh   ssh                       23.11.26, 17:09 YES 

Keys can be deleted with

% sc_auth delete-ctk-identity -h <Public Key Hash>

Usage with ssh

You can "download" the public / private keypair from the secure enclave using the following command:

% ssh-keygen -w /usr/lib/ssh-keychain.dylib -K -N ""
Enter PIN for authenticator: 
You may need to touch your authenticator to authorize key download.
Saved ECDSA-SK key to id_ecdsa_sk_rk
% cat id_ecdsa_sk_rk.pub 
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBKiHAiAZhcsZ95n85dkNGs9GnbDt0aNOia2gnuknYV2wKL3y0u+d3QrE9cFkmWXIymHZMglL+uJA+6mShY8SeykAAAAEc3NoOg== ssh:

You can just use the empty string for PIN. For some reason openssh always asks for it even if the authenticator in question does not use a PIN but a biometric. Note that the "private" key here is just a reference to the FIDO credential. It does not contain any secret key material. Hence I'm specifiyng -N "" to skip an encryption passphrase.

Now if you copy this public key to your authorized keys file, it should work!

% ssh-copy-id -i id_ecdsa_sk_rk localhost
% ssh -o SecurityKeyProvider=/usr/lib/ssh-keychain.dylib localhost

Usage with ssh-agent

Instead of downloading the public/private keypair to a file you can also directly make the keys available to ssh-agent. For this you can use the following command:

% ssh-add -K -S /usr/lib/ssh-keychain.dylib
Enter PIN for authenticator: 
Resident identity added: ECDSA-SK SHA256:vs4ByYo+T9M3V8iiDYONMSvx2k5Fj2ujVBWt1j6yzis
% ssh-add -L
sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBKiHAiAZhcsZ95n85dkNGs9GnbDt0aNOia2gnuknYV2wKL3y0u+d3QrE9cFkmWXIymHZMglL+uJA+6mShY8SeykAAAAEc3NoOg== 
% ssh-copy-id localhost
% ssh -o SecurityKeyProvider=/usr/lib/ssh-keychain.dylib localhost

Using the SecurityKeyProvider by default

SecurityKeyProvider can be configured in .ssh/config but I recommend setting export SSH_SK_PROVIDER=/usr/lib/ssh-keychain.dylib in your .zprofile instead as that environment variable gets picked up by ssh, ssh-add and ssh-keygen.

This means you can just do:

ssh-add -K
ssh my-server

or

ssh-keygen -K
ssh -i id_ecdsa_rk_sk my-server

to ssh into your server

Exportable keys

There's also an exportable variant where the private key is encrypted using the secure enclave as opposed to generated on the secure enclave. This is might be considered less secure but is convenient for key backup.

% sc_auth create-ctk-identity -l ssh-exportable -k p-256 -t bio
% sc_auth list-ctk-identities
p-256    A581E5404ED157C4C73FFDBDFC1339E0D873FCAE bio  ssh-exportable ssh-exportable               23.11.26, 19:50 YES  
% sc_auth export-ctk-identity -h A581E5404ED157C4C73FFDBDFC1339E0D873FCAE -f ssh-exportable.pem
Enter a password which will be used to protect the exported items:
Verify password:

You can then re-import it on another device

% sc_auth import-ctk-identities -f ssh-exportable.pem.p12 -t bio
Enter PKCS12 file password:
@arianvp
Copy link
Author

arianvp commented Nov 25, 2025

I personally solve this by using an ssh CA (stored on a Yubikey) that I use to sign the keys of each of my devices. My server only has the CA configured; not the specific keys

@maxgoedjen
Copy link

FWIW I'd be cautious about using the exportable key type (especially without the bio parameter) – best I can tell there's nothing to stop a random app from just shelling out with an export command and dumping your key, security-wise I think that shakes out roughly equivalent to just having it on disk.

@mischif
Copy link

mischif commented Nov 26, 2025

Is there a way to tell by pubkey whether a keypair was generated/stored in a secure enclave? Requiring SE-resident keys sounds like a good defense against an admin getting compromised immediately leading to prod access.

@arianvp
Copy link
Author

arianvp commented Nov 26, 2025

No. Apple does not open up attestation to the public. It's a private API only available to apple :(

@arianvp
Copy link
Author

arianvp commented Nov 28, 2025

For documentation sake; this is the page that announced the support for these tokens: https://support.apple.com/guide/deployment/use-persistent-tokens-dep4e2622249/1/web/1.0

@gitkass
Copy link

gitkass commented Nov 28, 2025

Howdy Arian, this is a great find. I've returned to this gist for reference a few times, so I wanted to highlight some info in the hope that it helps others.

your_key_type="p-256-ne" # The `-ne` suffix means non-exportable.
touch="bio" # or `none` to not require a touchID prompt 
your_label="key11"
your_email="190964064+gitkass@users.noreply.github.com"
your_common_name="kass.zxcv.foo"
your_organizational_unit="gitkass"
your_organization="zxcv.foo"
your_locality="Toronto"
your_state="Ontario"
your_country="CA" # (2-letter ISO 3166 country code, e.g., "US", "GB", "DE")

sc_auth create-ctk-identity \
-l "${your_label}" \
-N "${your_common_name}" \
-t "${touch}" \
-E "${your_email}" \
-U "${your_organizational_unit}" \
-O "${your_organization}" \
#-S "${your_state}" \
#-L "${your_locality}" \
#-C "${your_country}" \
-k "${your_key_type}" # Last since key type is required and other arguments may be commented out.

Manpage snippit:

COMMANDS - CTK Identity
     create-ctk-identity -l label -k p-256|p-384|p-521|p-256-ne|p-384-ne [-t bio|none] [-N CN] [-E
     emailAddress] [-U OU] [-O O] [-L L] [-S ST] [-C C] Create an CTK Identity.

            -l label
                     Specifies the key label
            -k p-256|p-384|p-521|p-256-ne|p-384-ne
                     Specifies the key type. The "-ne" suffix means non-exportable variant of key
            -t bio|none
                     Specifies private key protection
            -N CN    Specifies certificate Common Name. If not specified the label is used instead
            -E emailAddress
                     Specifies certificate Email Address
            -U OU    Specifies certificate Organizational Unit Name
            -O O     Specifies certificate Organization Name
            -L L     Specifies certificate Locality Name
            -S ST    Specifies certificate State Or Province Name
            -C C     Specifies certificate Country Name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment