FiloSottile/age · error

RSA key size is too small

Error message

RSA key size is too small

What it means

NewRSARecipient enforces a minimum modulus size of 2048 bits before accepting the key, because RSA-1024 and smaller are considered insecure. The key is otherwise a valid ssh-rsa key but is rejected at construction time. The same check is repeated in Wrap (error [4]) for defense in depth.

Source

Thrown at agessh/agessh.go:67

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))
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to b74dce4cdb)

Solutions

  1. Generate a new key with at least 2048 bits, preferably 4096: ssh-keygen -t rsa -b 4096.
  2. Re-encrypt the data to the new recipient key and retire the weak key.
  3. If you control key generation in Go, use rsa.GenerateKey(rand.Reader, 4096) and ssh.NewPublicKey.

Example fix

// before
ssh-keygen -t rsa -b 1024 -f id_rsa // rejected
// after
ssh-keygen -t rsa -b 4096 -f id_rsa
Defensive patterns

Strategy: validation

Validate before calling

func checkRSASize(pk ssh.PublicKey) error {
    cpk, ok := pk.(ssh.CryptoPublicKey)
    if !ok { return errors.New("no crypto key") }
    r, ok := cpk.CryptoPublicKey().(*rsa.PublicKey)
    if !ok { return errors.New("not RSA") }
    if r.N.BitLen() < 2048 {
        return fmt.Errorf("RSA key has %d bits; need >= 2048", r.N.BitLen())
    }
    return nil
}

Type guard

func isStrongRSA(pk ssh.PublicKey) bool {
    cpk, ok := pk.(ssh.CryptoPublicKey)
    if !ok { return false }
    r, ok := cpk.CryptoPublicKey().(*rsa.PublicKey)
    return ok && r.N.BitLen() >= 2048
}

Try / catch

rec, err := agessh.NewRSARecipient(pk)
if err != nil {
    if strings.Contains(err.Error(), "key size is too small") {
        return fmt.Errorf("rotate key %s: RSA < 2048 bits", sshFingerprint(pk))
    }
    return err
}

Prevention

When it happens

Trigger: Calling agessh.NewRSARecipient(pk) with an ssh-rsa key whose rsa.PublicKey.N.BitLen() < 2048 — e.g. a 1024-bit (or 768-bit) RSA key.

Common situations: Legacy authorized_keys entries generated in the 2000s with ssh-keygen -t rsa -b 1024; old automated/deployment keys never rotated; corporate keys grandfathered from pre-2013 standards.

Related errors


AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31). Data as JSON: /api/errors/ab0a69fb1e61d4d0. Report an issue: GitHub.