golang/go · error

crypto/ecdh: invalid private key

Error message

crypto/ecdh: invalid private key

What it means

Thrown by fips140/ecdh.NewPrivateKey when the key byte slice fails SP 800-56A Rev. 3 §5.6.1.2.2: the length must equal len(c.N), the bytes must not be all zero (isZero), and the value must be strictly less than the curve order n (isLess). Equivalently the private scalar d must satisfy 0 < d < n.

Source

Thrown at src/crypto/internal/fips140/ecdh/ecdh.go:194

			if err != nil {
				return err
			}
			if !bytes.Equal(p1.Bytes(), privateKey.pub.q) {
				return errors.New("crypto/ecdh: public key does not match private key")
			}
			return nil
		})

		return privateKey, nil
	}
}

func NewPrivateKey[P Point[P]](c *Curve[P], key []byte) (*PrivateKey, error) {
	// SP 800-56A Rev. 3, Section 5.6.1.2.2 checks that c <= n – 2 and then
	// returns d = c + 1. Note that it follows that 0 < d < n. Equivalently,
	// we check that 0 < d < n, and return d.
	if len(key) != len(c.N) || isZero(key) || !isLess(key, c.N) {
		return nil, errors.New("crypto/ecdh: invalid private key")
	}

	p, err := c.newPoint().ScalarBaseMult(key)
	if err != nil {
		// This is unreachable because the only error condition of
		// ScalarBaseMult is if the input is not the right size.
		panic("crypto/ecdh: internal error: nistec ScalarBaseMult failed for a fixed-size input")
	}

	publicKey := p.Bytes()
	if len(publicKey) == 1 {
		// The encoding of the identity is a single 0x00 byte. This is
		// unreachable because the only scalar that generates the identity is
		// zero, which is rejected above.
		panic("crypto/ecdh: internal error: public key is the identity element")
	}

	k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Confirm the key length matches the curve order length (e.g. 32 bytes for P-256, 48 for P-384, 66 for P-521).
  2. Regenerate the key via the proper GenerateKey path rather than supplying raw bytes.
  3. If sourcing bytes from another library, reduce them mod n (or reduce-and-add-1 per SP 800-56A) and ensure the result is non-zero and < n.

Example fix

// before
priv, err := ecdh.NewPrivateKey(curve, rawBytes) // rawBytes may be wrong length / >= n

// after: derive through the approved generator
priv, err := curve.GenerateKey(rand.Reader)
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check SP 800-56A preconditions before NewPrivateKey.
if len(key) != len(curveN) {
    return fmt.Errorf("private key must be %d bytes", len(curveN))
}
if allZero(key) {
    return errors.New("private key is zero")
}
if !bytesLess(key, curveN) {
    return errors.New("private key >= curve order")
}

Type guard

func validECDHScalar(key, order []byte) bool {
    if len(key) != len(order) || allZero(key) { return false }
    return bytes.Compare(key, order) < 0
}

Try / catch

priv, err := ecdh.NewPrivateKey(curve, key)
if err != nil {
    // regenerate from the approved RNG rather than retrying the same bytes
    priv, err = curve.GenerateKey(rand.Reader)
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Constructing an ECDH private key whose byte length differs from the curve order, is all zeros, or represents a value >= n. Reached through crypto/ecdh which backs onto this FIPS implementation.

Common situations: Importing a scalar generated for a different curve, a truncated or zeroed key buffer, or a scalar that happens to be >= the order after reduction/masking.

Related errors


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