FiloSottile/age · error
SSH public key is not an Ed25519 key
Error message
SSH public key is not an Ed25519 key
What it means
NewEd25519Recipient accepts only SSH keys of type "ssh-ed25519". This error is returned when the passed ssh.PublicKey has a different wire type — e.g. ssh-rsa, ECDSA (ecdsa-sha2-nistp*), or an OpenSSH certificate. Like the RSA counterpart, it is upfront key-type validation before any curve25519 conversion.
Source
Thrown at agessh/agessh.go:151
block.Body, []byte(oaepLabel))
if err != nil {
// The fingerprint is only a short hint, and might collide with the
// fingerprint of a different recipient.
return nil, age.ErrIncorrectIdentity
}
return fileKey, nil
}
type Ed25519Recipient struct {
sshKey ssh.PublicKey
theirPublicKey []byte
}
var _ age.Recipient = &Ed25519Recipient{}
func NewEd25519Recipient(pk ssh.PublicKey) (*Ed25519Recipient, error) {
if pk.Type() != "ssh-ed25519" {
return nil, errors.New("SSH public key is not an Ed25519 key")
}
cpk, ok := pk.(ssh.CryptoPublicKey)
if !ok {
return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
}
epk, ok := cpk.CryptoPublicKey().(ed25519.PublicKey)
if !ok {
return nil, errors.New("unexpected public key type")
}
mpk, err := ed25519PublicKeyToCurve25519(epk)
if err != nil {
return nil, fmt.Errorf("invalid Ed25519 public key: %v", err)
}
return &Ed25519Recipient{
sshKey: pk,
theirPublicKey: mpk,View on GitHub (pinned to b74dce4cdb)
Solutions
- Check pk.Type() == "ssh-ed25519" before calling; route ssh-rsa keys to NewRSARecipient.
- Generate an Ed25519 key: ssh-keygen -t ed25519 -f id_ed25519.
- When parsing multi-key files, match the specific key line by fingerprint or type.
Example fix
// before
rec, err := agessh.NewEd25519Recipient(rsaPubKey)
// after
if rsaPubKey.Type() == "ssh-rsa" {
rec, err = agessh.NewRSARecipient(rsaPubKey)
} else if rsaPubKey.Type() == "ssh-ed25519" {
rec, err = agessh.NewEd25519Recipient(rsaPubKey)
} Defensive patterns
Strategy: validation
Validate before calling
if pk.Type() != "ssh-ed25519" {
return fmt.Errorf("expected ssh-ed25519 key, got %s", pk.Type())
} Type guard
func isSSHEd25519(pk ssh.PublicKey) bool { return pk != nil && pk.Type() == "ssh-ed25519" } Try / catch
rec, err := agessh.NewEd25519Recipient(pk)
if err != nil {
if strings.Contains(err.Error(), "not an Ed25519 key") {
return fmt.Errorf("recipient key %s is not Ed25519", pk.Type())
}
return err
} Prevention
- Route ssh-rsa keys to NewRSARecipient based on pk.Type().
- Standardize on ssh-keygen -t ed25519 for new keys.
- Filter multi-key files by key type before recipient construction.
- Match encrypted identity files to the correct constructor (NewEncryptedSSHIdentity expects the right algo).
When it happens
Trigger: Calling agessh.NewEd25519Recipient(pk) with pk.Type() != "ssh-ed25519" — directly, via agessh.ParseRecipient on a non-Ed25519 authorized_keys entry, or via agessh.NewEncryptedSSHIdentity wrapping a non-Ed25519 private key.
Common situations: Iterating an authorized_keys file and assuming all keys are Ed25519; passing an RSA key because the code path was copied from an RSA implementation; encrypted identity files generated from RSA private keys.
Related errors
- SSH public key is not an RSA key
- unexpected public key type
- pk does not implement ssh.CryptoPublicKey
- RSA key size is too small
- invalid Ed25519 public key: %v
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/3f29bc09231a9518.
Report an issue: GitHub.