golang/go · critical

crypto/rsa: d does not match dP

Error message

crypto/rsa: d does not match dP

What it means

Thrown when d mod (p-1) does not equal the stored dP. The library recomputes the expected dP from d and compares; a mismatch means d and the CRT exponent dP disagree, so the key is internally inconsistent (even though dP may satisfy e*dP ≡ 1 mod p-1 on its own).

Source

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

	if de.IsOne() != 1 {
		return errors.New("crypto/rsa: invalid CRT exponent")
	}

	// Check that qInv * q ≡ 1 mod p.
	qP, err := bigmod.NewNat().SetOverflowingBytes(q.Nat().Bytes(q), p)
	if err != nil {
		// q >= 2^⌈log2(p)⌉
		qP = bigmod.NewNat().Mod(q.Nat(), p)
	}
	if qP.Mul(priv.qInv, p).IsOne() != 1 {
		return errors.New("crypto/rsa: invalid CRT coefficient")
	}

	// Check d against dP and dQ, even though we never actually use d,
	// to make sure the key is consistent.
	dP1 := bigmod.NewNat().Mod(priv.d, pMinus1)
	if dP1.Equal(dP) != 1 {
		return errors.New("crypto/rsa: d does not match dP")
	}
	dQ1 := bigmod.NewNat().Mod(priv.d, qMinus1)
	if dQ1.Equal(dQ) != 1 {
		return errors.New("crypto/rsa: d does not match dQ")
	}

	// Check that |p - q| > 2^(nlen/2 - 100).
	//
	// If p and q are very close to each other, then N=pq can be trivially
	// factored using Fermat's factorization method. Broken RSA implementations
	// do generate such keys. See Hanno Böck, Fermat Factorization in the Wild,
	// https://eprint.iacr.org/2023/026.pdf.
	diff := bigmod.NewNat()
	if qP, err := bigmod.NewNat().SetBytes(q.Nat().Bytes(q), p); err != nil {
		// q > p
		pQ, err := bigmod.NewNat().SetBytes(p.Nat().Bytes(p), q)
		if err != nil {
			return errors.New("crypto/rsa: p == q")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Recompute dP = d mod (p-1) (and dQ = d mod (q-1)) whenever d changes, or re-import the whole key.
  2. Regenerate the key pair.
  3. Treat d, p, q, dP, dQ, qInv as atomic — never edit one without the others.

Example fix

// before
// d updated, dP left stale

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

Strategy: validation

Validate before calling

pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
expected := new(big.Int).Mod(d, pMinus1)
if expected.Cmp(dP) != 0 {
    return errors.New("d mod (p-1) != dP")
}

Type guard

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

Try / catch

err := validateKey(priv)
if err != nil && strings.Contains(err.Error(), "d does not match dP") {
    return err // recompute dP from d, or regenerate
}

Prevention

When it happens

Trigger: Key validation's final consistency check: dP1 = d mod (p-1); dP1.Equal(dP) != 1. Reached after all prior CRT checks passed.

Common situations: d replaced/edited without recomputing dP. Key assembled from a d belonging to a different key than p. A partially-redacted or migrated key.

Related errors


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