Convert SSH Ed25519 keys to age keys. This is useful for usage in sops-nix and sops
Go
222
404 commits
updated Sep 20, 2026
Convert SSH Ed25519 keys to age keys. This is useful for usage in sops-nix and sops
$ ssh-to-age -private-key -i $HOME/.ssh/id_ed25519 -o key.txt
$ cat key.txt
AGE-SECRET-KEY-1K3VN4N03PTHJWSJSCCMQCN33RY5FSKQPJ4KRRTG3JMQUYE0TUSEQEDH6V8
If your private key is encrypted, pipe the passphrase via -stdinpass:
$ systemd-ask-password | ssh-to-age -private-key -stdinpass -i $HOME/.ssh/id_ed25519 -o key.txt
$ security find-generic-password -w -s 'SSH Key Passphrase' | ssh-to-age -private-key -stdinpass -i $HOME/.ssh/id_ed25519 -o key.txt
Alternatively, pass it via SSH_TO_AGE_PASSPHRASE environment variable.
[!NOTE] Go's
golang.org/x/crypto/sshcan only decrypt OpenSSH private keys that use theaes256-ctroraes256-cbccipher. If your key was generated withssh-keygen -Z chacha20-poly1305@openssh.com(oraes*-gcm@openssh.com),ssh-to-agewill fail withunknown cipher/parse error in message type 0. Re-encrypt a copy of the key with a supported cipher (the Ed25519 key material and thus the resulting age key stay identical):$ install -m600 $HOME/.ssh/id_ed25519 /tmp/id_ed25519 $ ssh-keygen -p -f /tmp/id_ed25519 -Z aes256-ctr $ ssh-to-age -private-key -i /tmp/id_ed25519 -o key.txt $ rm /tmp/id_ed25519
$ ssh-to-age -i $HOME/.ssh/id_ed25519.pub -o pub-key.txt
$ cat pub-key.txt
age17044m9wgakla6pzftf4srtl3h5mcsr85jysgt5fg23zpnta8sfdqhzn452
ssh-to-age also supports multiple public keys at once seperated by newlines and ignores unless ssh keys that are not in the ed25519 format. This makes it suiteable in combination with ssh-keyscan:
$ ssh-keyscan eve.thalheim.io eva.thalheim.io | ssh-to-age
# eve.thalheim.io:22 SSH-2.0-OpenSSH_8.6
...
age1hjm3aujg9e79f5yth8a2cejzdjg5n9vnu96l05p70uvfpeltnpms7yy3pp
age1v8zjc47jmlqwefyu66s0d4ke98qr4vnuj3cpvs4z9npfdw833dxqwjrhzv
ssh-to-age deterministically derives an X25519 (age) key from an Ed25519 SSH
key using the standard Ed25519-to-X25519 birational map (see
Filippo Valsorda's write-up):
sha512(seed)[:32] — the same scalar Ed25519
itself derives from the seed per RFC 8032. No new key material is generated.A common pattern is using a passphrase-protected SSH key together with an
unencrypted age key for sops / sops-nix. Be aware that:
If you want the age key to inherit the at-rest protection of your SSH key,
do not derive it from that SSH key. Generate a dedicated age identity and
protect it separately — for example with
age-plugin-yubikey,
age-plugin-tpm, or by keeping
it on an encrypted volume that is unlocked on demand.
$ nix-shell -p 'import (fetchTarball "https://github.com/Mic92/ssh-to-age/archive/main.tar.gz") {}'
$ go install github.com/Mic92/ssh-to-age/cmd/ssh-to-age@latest
139 commits
135 commits
63 commits
42 commits
Go
73.5%
Nix
18.7%
Shell
7.8%
Convert SSH Ed25519 keys to age keys. This is useful for usage in sops-nix and sops
Go
222
404 commits
updated Sep 20, 2026
Convert SSH Ed25519 keys to age keys. This is useful for usage in sops-nix and sops
$ ssh-to-age -private-key -i $HOME/.ssh/id_ed25519 -o key.txt
$ cat key.txt
AGE-SECRET-KEY-1K3VN4N03PTHJWSJSCCMQCN33RY5FSKQPJ4KRRTG3JMQUYE0TUSEQEDH6V8
If your private key is encrypted, pipe the passphrase via -stdinpass:
$ systemd-ask-password | ssh-to-age -private-key -stdinpass -i $HOME/.ssh/id_ed25519 -o key.txt
$ security find-generic-password -w -s 'SSH Key Passphrase' | ssh-to-age -private-key -stdinpass -i $HOME/.ssh/id_ed25519 -o key.txt
Alternatively, pass it via SSH_TO_AGE_PASSPHRASE environment variable.
[!NOTE] Go's
golang.org/x/crypto/sshcan only decrypt OpenSSH private keys that use theaes256-ctroraes256-cbccipher. If your key was generated withssh-keygen -Z chacha20-poly1305@openssh.com(oraes*-gcm@openssh.com),ssh-to-agewill fail withunknown cipher/parse error in message type 0. Re-encrypt a copy of the key with a supported cipher (the Ed25519 key material and thus the resulting age key stay identical):$ install -m600 $HOME/.ssh/id_ed25519 /tmp/id_ed25519 $ ssh-keygen -p -f /tmp/id_ed25519 -Z aes256-ctr $ ssh-to-age -private-key -i /tmp/id_ed25519 -o key.txt $ rm /tmp/id_ed25519
$ ssh-to-age -i $HOME/.ssh/id_ed25519.pub -o pub-key.txt
$ cat pub-key.txt
age17044m9wgakla6pzftf4srtl3h5mcsr85jysgt5fg23zpnta8sfdqhzn452
ssh-to-age also supports multiple public keys at once seperated by newlines and ignores unless ssh keys that are not in the ed25519 format. This makes it suiteable in combination with ssh-keyscan:
$ ssh-keyscan eve.thalheim.io eva.thalheim.io | ssh-to-age
# eve.thalheim.io:22 SSH-2.0-OpenSSH_8.6
...
age1hjm3aujg9e79f5yth8a2cejzdjg5n9vnu96l05p70uvfpeltnpms7yy3pp
age1v8zjc47jmlqwefyu66s0d4ke98qr4vnuj3cpvs4z9npfdw833dxqwjrhzv
ssh-to-age deterministically derives an X25519 (age) key from an Ed25519 SSH
key using the standard Ed25519-to-X25519 birational map (see
Filippo Valsorda's write-up):
sha512(seed)[:32] — the same scalar Ed25519
itself derives from the seed per RFC 8032. No new key material is generated.A common pattern is using a passphrase-protected SSH key together with an
unencrypted age key for sops / sops-nix. Be aware that:
If you want the age key to inherit the at-rest protection of your SSH key,
do not derive it from that SSH key. Generate a dedicated age identity and
protect it separately — for example with
age-plugin-yubikey,
age-plugin-tpm, or by keeping
it on an encrypted volume that is unlocked on demand.
$ nix-shell -p 'import (fetchTarball "https://github.com/Mic92/ssh-to-age/archive/main.tar.gz") {}'
$ go install github.com/Mic92/ssh-to-age/cmd/ssh-to-age@latest
139 commits
135 commits
63 commits
42 commits
Go
73.5%
Nix
18.7%
Shell
7.8%