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
- Confirm the key length matches the curve order length (e.g. 32 bytes for P-256, 48 for P-384, 66 for P-521).
- Regenerate the key via the proper GenerateKey path rather than supplying raw bytes.
- 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
- Generate ECDH keys via GenerateKey rather than importing raw bytes.
- When importing, validate length, non-zero, and < n before calling NewPrivateKey.
- Tag imported key material with its source curve.
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
- crypto/ecdh: invalid public key
- crypto/ecdh: mismatched curves
- crypto/ecdh: public key is the identity element
- crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-
- crypto/ecdh: use of X25519 is not allowed in FIPS 140-only m
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/a1d79ecfe2c38f28.
Report an issue: GitHub.