golang/go · critical

crypto/rsa: invalid CRT exponent

Error message

crypto/rsa: invalid CRT exponent

What it means

Thrown when SetBytes rejects priv.dP into the (p-1) modulus, meaning dP >= p-1 or its byte length is wrong. dP is d mod (p-1), a CRT exponent, and must be a valid residue in [0, p-1). An out-of-range dP means the stored CRT params are inconsistent with p.

Source

Thrown at src/crypto/internal/fips140/rsa/rsa.go:243

	if pN.Mul(qN, N).IsZero() != 1 {
		return errors.New("crypto/rsa: p * q != n")
	}

	// Check that de ≡ 1 mod p-1, and de ≡ 1 mod q-1.
	//
	// This implies that e is coprime to each p-1 as e has a multiplicative
	// inverse. Therefore e is coprime to lcm(p-1,q-1) = λ(N).
	// It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1 mod p. Thus a^de ≡ a
	// mod n for all a coprime to n, as required.
	//
	// This checks dP, dQ, and e.
	pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p))
	if err != nil {
		return errors.New("crypto/rsa: invalid prime")
	}
	dP, err := bigmod.NewNat().SetBytes(priv.dP, pMinus1)
	if err != nil {
		return errors.New("crypto/rsa: invalid CRT exponent")
	}
	de := bigmod.NewNat()
	de.SetUint(uint(priv.pub.E)).ExpandFor(pMinus1)
	de.Mul(dP, pMinus1)
	if de.IsOne() != 1 {
		return errors.New("crypto/rsa: invalid CRT exponent")
	}

	qMinus1, err := bigmod.NewModulus(q.Nat().SubOne(q).Bytes(q))
	if err != nil {
		return errors.New("crypto/rsa: invalid prime")
	}
	dQ, err := bigmod.NewNat().SetBytes(priv.dQ, qMinus1)
	if err != nil {
		return errors.New("crypto/rsa: invalid CRT exponent")
	}
	de.SetUint(uint(priv.pub.E)).ExpandFor(qMinus1)
	de.Mul(dQ, qMinus1)

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Recompute dP = d mod (p-1) from the true d and p and overwrite the stored value, or re-import the full key.
  2. Regenerate the key pair entirely.
  3. Use a standard parser that round-trips all CRT fields atomically.

Example fix

// before
// dP loaded independently and inconsistent with p

// after
pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
dP := new(big.Int).Mod(d, pMinus1)
// store dP alongside the matching p
Defensive patterns

Strategy: validation

Validate before calling

pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
if new(big.Int).Mod(dP, pMinus1).Cmp(dP) != 0 || dP.Cmp(pMinus1) >= 0 {
    return errors.New("dP must be a valid residue mod (p-1)")
}

Type guard

func dPValidForP(dP, p *big.Int) bool {
    pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
    return dP.Cmp(pMinus1) < 0
}

Try / catch

err := validateKey(priv)
if err != nil && strings.Contains(err.Error(), "invalid CRT exponent") {
    // recompute dP from d and p, or regenerate
    return err
}

Prevention

When it happens

Trigger: Key validation's CRT exponent check: dP does not fit modulus (p-1). Reached after the (p-1) modulus was successfully built.

Common situations: dP computed against a different p than the one stored (mismatched CRT params). dP byte slice truncated or extended during serialization. Key assembled from parts of different keys.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/f78cff5a7886f18f. Report an issue: GitHub.