{"record":{"id":"3f29bc09231a9518","repo":"FiloSottile/age","slug":"ssh-public-key-is-not-an-ed25519-key","errorCode":null,"errorMessage":"SSH public key is not an Ed25519 key","messagePattern":"SSH public key is not an Ed25519 key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agessh/agessh.go","lineNumber":151,"sourceCode":"\t\tblock.Body, []byte(oaepLabel))\n\tif err != nil {\n\t\t// The fingerprint is only a short hint, and might collide with the\n\t\t// fingerprint of a different recipient.\n\t\treturn nil, age.ErrIncorrectIdentity\n\t}\n\treturn fileKey, nil\n}\n\ntype Ed25519Recipient struct {\n\tsshKey         ssh.PublicKey\n\ttheirPublicKey []byte\n}\n\nvar _ age.Recipient = &Ed25519Recipient{}\n\nfunc NewEd25519Recipient(pk ssh.PublicKey) (*Ed25519Recipient, error) {\n\tif pk.Type() != \"ssh-ed25519\" {\n\t\treturn nil, errors.New(\"SSH public key is not an Ed25519 key\")\n\t}\n\n\tcpk, ok := pk.(ssh.CryptoPublicKey)\n\tif !ok {\n\t\treturn nil, errors.New(\"pk does not implement ssh.CryptoPublicKey\")\n\t}\n\tepk, ok := cpk.CryptoPublicKey().(ed25519.PublicKey)\n\tif !ok {\n\t\treturn nil, errors.New(\"unexpected public key type\")\n\t}\n\tmpk, err := ed25519PublicKeyToCurve25519(epk)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid Ed25519 public key: %v\", err)\n\t}\n\n\treturn &Ed25519Recipient{\n\t\tsshKey:         pk,\n\t\ttheirPublicKey: mpk,","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/agessh/agessh.go#L133-L169","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nrec, err := agessh.NewEd25519Recipient(rsaPubKey)\n// after\nif rsaPubKey.Type() == \"ssh-rsa\" {\n    rec, err = agessh.NewRSARecipient(rsaPubKey)\n} else if rsaPubKey.Type() == \"ssh-ed25519\" {\n    rec, err = agessh.NewEd25519Recipient(rsaPubKey)\n}","handlingStrategy":"validation","validationCode":"if pk.Type() != \"ssh-ed25519\" {\n    return fmt.Errorf(\"expected ssh-ed25519 key, got %s\", pk.Type())\n}","typeGuard":"func isSSHEd25519(pk ssh.PublicKey) bool { return pk != nil && pk.Type() == \"ssh-ed25519\" }","tryCatchPattern":"rec, err := agessh.NewEd25519Recipient(pk)\nif err != nil {\n    if strings.Contains(err.Error(), \"not an Ed25519 key\") {\n        return fmt.Errorf(\"recipient key %s is not Ed25519\", pk.Type())\n    }\n    return err\n}","preventionTips":["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)."],"tags":["ssh","ed25519","key-type","age"],"backgroundTag":"ssh-key-type-mismatch","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}