FiloSottile/age · error
pk does not implement ssh.CryptoPublicKey
Error message
pk does not implement ssh.CryptoPublicKey
What it means
NewRSARecipient requires the ssh.PublicKey to implement the ssh.CryptoPublicKey interface so the raw *rsa.PublicKey can be extracted. This error means the value passed does not implement that interface, so age cannot perform RSA-OAEP encryption. With keys from the standard x/crypto/ssh parsers this essentially never happens; it indicates a hand-rolled ssh.PublicKey type.
Source
Thrown at agessh/agessh.go:64
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, nil
}
func (r *RSARecipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
if r.pubKey.N.BitLen() < 2048 {
return nil, errors.New("RSA key size is too small")
}
l := &age.Stanza{
Type: "ssh-rsa",
Args: []string{sshFingerprint(r.sshKey)},
}
wrappedKey, err := rsa.EncryptOAEP(sha256.New(), rand.Reader,
r.pubKey, fileKey, []byte(oaepLabel))View on GitHub (pinned to b74dce4cdb)
Solutions
- Use keys from golang.org/x/crypto/ssh (ParsePublicKey, NewPublicKey, ParseAuthorizedKey), which implement CryptoPublicKey.
- Add a CryptoPublicKey() crypto.PublicKey method returning the *rsa.PublicKey to your custom key type.
- Guard with a type assertion to ssh.CryptoPublicKey before calling.
Example fix
// before
rec, err := agessh.NewRSARecipient(pk) // pk is a custom type
// after
if _, ok := pk.(ssh.CryptoPublicKey); !ok {
return fmt.Errorf("unsupported ssh public key type %T", pk)
}
rec, err = agessh.NewRSARecipient(pk) Defensive patterns
Strategy: type-guard
Validate before calling
if _, ok := pk.(ssh.CryptoPublicKey); !ok {
return fmt.Errorf("key type %T does not implement ssh.CryptoPublicKey", pk)
} Type guard
func supportsCrypto(pk ssh.PublicKey) bool {
_, ok := pk.(ssh.CryptoPublicKey)
return ok
} Try / catch
rec, err := agessh.NewRSARecipient(pk)
if err != nil {
if strings.Contains(err.Error(), "ssh.CryptoPublicKey") {
return fmt.Errorf("unsupported key implementation %T", pk)
}
return err
} Prevention
- Obtain ssh.PublicKey values exclusively from golang.org/x/crypto/ssh parsers.
- Avoid mocks/fakes for ssh.PublicKey in production code paths.
- Add compile-time assertions: var _ ssh.CryptoPublicKey = (*myKey)(nil).
When it happens
Trigger: Calling agessh.NewRSARecipient(pk) where pk does not implement ssh.CryptoPublicKey() crypto.PublicKey — only realistic with a custom ssh.PublicKey implementation or a nil/malformed wrapper.
Common situations: Mock ssh.PublicKey values in tests; keys constructed by third-party SSH libraries that do not implement CryptoPublicKey.
Related errors
- unexpected public key type
- RSA key size is too small
- SSH public key is not an RSA key
- SSH public key is not an Ed25519 key
- mismatched private and public SSH key
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/0581c21993622ec7.
Report an issue: GitHub.