TL;DR

ssh-keygen -a 120 -C "auth@aarons-mac-mini" -f ~/.ssh/id_ed25519
ssh-keygen -a 120 -C "sign@aarons-mac-mini" -f ~/.ssh/id_ed25519_sign

Guidelines

  • Use unique key pairs per device
  • Separate keys based on purpose:
    • auth (authentication)
    • sign (signing)
  • Key generation and storage:
    • Prefer secure enclave if available (no passphrase needed)
    • Otherwise, use Ed25519 keys on the local filesystem with a strong passphrase
  • Increase key derivation function (KDF) rounds to 120 for enhanced security
  • Comment format:
    • Desktop devices: <PURPOSE>@<HOSTNAME> (e.g., auth@aarons-mac-mini)
    • Mobile devices: <PURPOSE>@<APPLICATION>.<HOSTNAME> (e.g., sign@working-copy.aarons-ipad)
  • File naming convention:
    • ~/.ssh/id_ed25519 for auth key (default lookup location)
    • ~/.ssh/id_ed25519_sign for sign key (must be explicitly referenced)
  • When adding a key into your SSH agent via ssh-add:
    • Set a maximum lifetime with -t <LIFE> to expire your credentials and force a passphrase prompt after the given amount of time.
    • Store the passphrase in a password manager (you should not need to memorize it). I personally use the script bw-ssh-add to automate pulling it from Bitwarden.

KDF Rounds Benchmark

The upstream default for KDF rounds is currently 24 (it’s only 16 on macOS). Higher numbers increase resistance to brute-force attacks but slow down passphrase verification. Choose the highest number you can tolerate.

To benchmark KDF rounds using hyperfine on your own hardware, use the following commands:

$ ssh-keygen -q -f ./intial_key -N ""
 
$ hyperfine -L rounds 24,48,96,120 --prepare "cp initial_key test_key" --cleanup "rm test_key" "yes | ssh-keygen -p -a {rounds} -f test_key -N 'password' -P ''"
Benchmark 1: yes | ssh-keygen -p -a 24 -f test_key -N 'password' -P ''
  Time (mean ± σ):     170.7 ms ±   0.7 ms    [User: 166.9 ms, System: 3.4 ms]
  Range (min … max):   169.6 ms … 171.9 ms    16 runs
 
Benchmark 2: yes | ssh-keygen -p -a 48 -f test_key -N 'password' -P ''
  Time (mean ± σ):     338.7 ms ±   1.2 ms    [User: 332.8 ms, System: 5.4 ms]
  Range (min … max):   337.4 ms … 340.5 ms    10 runs
 
Benchmark 3: yes | ssh-keygen -p -a 96 -f test_key -N 'password' -P ''
  Time (mean ± σ):     674.0 ms ±   2.1 ms    [User: 665.0 ms, System: 8.4 ms]
  Range (min … max):   671.8 ms … 678.1 ms    10 runs
 
Benchmark 4: yes | ssh-keygen -p -a 120 -f test_key -N 'password' -P ''
  Time (mean ± σ):     840.8 ms ±   0.8 ms    [User: 830.2 ms, System: 10.1 ms]
  Range (min … max):   838.9 ms … 841.8 ms    10 runs
 
Summary
  yes | ssh-keygen -p -a 24 -f test_key -N 'password' -P '' ran
    1.98 ± 0.01 times faster than yes | ssh-keygen -p -a 48 -f test_key -N 'password' -P ''
    3.95 ± 0.02 times faster than yes | ssh-keygen -p -a 96 -f test_key -N 'password' -P ''
    4.93 ± 0.02 times faster than yes | ssh-keygen -p -a 120 -f test_key -N 'password' -P ''
 
$ rm initial_key{,.pub}

For posterity, this is the hardware I ran the above commands on:

$ system_profiler -detailLevel mini SPHardwareDataType SPSoftwareDataType
Hardware:
 
    Hardware Overview:
 
      Model Name: Mac mini
      Model Identifier: Mac14,12
      Model Number: MNH73LL/A
      Chip: Apple M2 Pro
      Total Number of Cores: 10 (6 performance and 4 efficiency)
      Memory: 16 GB
      System Firmware Version: 10151.140.19
      OS Loader Version: 10151.140.19
 
Software:
 
    System Software Overview:
 
      System Version: macOS 14.6 (23G80)
      Kernel Version: Darwin 23.6.0
      Time since boot: 3 days, 3 hours, 35 minutes
 
$ ssh -V
OpenSSH_9.7p1, LibreSSL 3.3.6
 
$ date -Ru
Fri, 09 Aug 2024 16:29:11 +0000