FiloSottile/age · error
SSH public key is not an RSA key
Error message
SSH public key is not an RSA key
What it means
NewRSARecipient wraps an ssh.PublicKey as an age RSA recipient, but only accepts keys whose SSH wire type is exactly "ssh-rsa". This error is returned when the key's Type() is something else, e.g. an Ed25519, ECDSA (ecdsa-sha2-nistp256), or certificate (cert-v01@openssh.com) key. It is a pure input-validation failure; the key object itself was rejected before any crypto is attempted.
Source
Thrown at agessh/agessh.go:51
)
func sshFingerprint(pk ssh.PublicKey) string {
h := sha256.Sum256(pk.Marshal())
return format.EncodeToString(h[:4])
}
const oaepLabel = "age-encryption.org/v1/ssh-rsa"
type RSARecipient struct {
sshKey ssh.PublicKey
pubKey *rsa.PublicKey
}
var _ age.Recipient = &RSARecipient{}
func NewRSARecipient(pk ssh.PublicKey) (*RSARecipient, error) {
if pk.Type() != "ssh-rsa" {
return nil, errors.New("SSH public key is not an RSA key")
}
r := &RSARecipient{
sshKey: pk,
}
if pk, ok := pk.(ssh.CryptoPublicKey); ok {
if pk, ok := pk.CryptoPublicKey().(*rsa.PublicKey); ok {
r.pubKey = pk
} else {
return nil, errors.New("unexpected public key type")
}
} else {
return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
}
if r.pubKey.N.BitLen() < 2048 {
return nil, errors.New("RSA key size is too small")
}
return r, nilView on GitHub (pinned to b74dce4cdb)
Solutions
- Check the key type before calling: only pass keys where pk.Type() == "ssh-rsa".
- If the key is intentionally Ed25519, use agessh.NewEd25519Recipient instead.
- When parsing a key file, select the ssh-rsa entry from the authorized_keys line rather than the first line.
- Generate a dedicated RSA key: ssh-keygen -t rsa -b 4096 -f key_rsa (with -m PEM if it must be an encrypted identity file).
Example fix
// before
rec, err := agessh.NewRSARecipient(pubKey) // pubKey is ssh-ed25519
// after
if pubKey.Type() == "ssh-rsa" {
rec, err = agessh.NewRSARecipient(pubKey)
} else if pubKey.Type() == "ssh-ed25519" {
rec, err = agessh.NewEd25519Recipient(pubKey)
} Defensive patterns
Strategy: validation
Validate before calling
if pk.Type() != "ssh-rsa" {
return fmt.Errorf("expected ssh-rsa key, got %s", pk.Type())
} Type guard
func isSSHRSA(pk ssh.PublicKey) bool { return pk != nil && pk.Type() == "ssh-rsa" } Try / catch
rec, err := agessh.NewRSARecipient(pk)
if err != nil {
if strings.Contains(err.Error(), "not an RSA key") {
// fall back to a type-appropriate recipient
}
return err
} Prevention
- Dispatch recipient construction on pk.Type() rather than hardcoding RSA.
- Filter authorized_keys entries by key type before parsing.
- Prefer x/crypto/ssh-parsed keys over hand-built ssh.PublicKey values.
- Document required key types wherever key files are configured.
When it happens
Trigger: Calling agessh.NewRSARecipient(pk) with pk.Type() != "ssh-rsa" — directly, or indirectly via agessh.ParseRecipient on an authorized_keys line for a non-RSA key, or via agessh.NewEncryptedSSHIdentity whose decrypted key is non-RSA.
Common situations: Parsing an authorized_keys/id_*.pub file containing multiple keys and passing an Ed25519 or ECDSA entry to NewRSARecipient; using an OpenSSH certificate instead of a raw public key; generating modern keys (ssh-keygen defaults to Ed25519 since OpenSSH 8.x) and assuming RSA.
Related errors
- SSH public key is not an Ed25519 key
- unexpected public key type
- pk does not implement ssh.CryptoPublicKey
- RSA key size is too small
- mismatched private and public SSH key
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/d1f62c6fa1c48c36.
Report an issue: GitHub.