golang/go · error

crypto/ecdh: invalid private key size

Error message

crypto/ecdh: invalid private key size

What it means

X25519 private keys are exactly 32 bytes (x25519PrivateKeySize). NewPrivateKey checks len(key) != x25519PrivateKeySize and returns this error for any other length. (The FIPS check runs first; this fires only when FIPS mode is not enforced.)

Source

Thrown at src/crypto/ecdh/x25519.go:54

func (c *x25519Curve) GenerateKey(r io.Reader) (*PrivateKey, error) {
	if fips140only.Enforced() {
		return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
	}
	r = rand.CustomReader(r)
	key := make([]byte, x25519PrivateKeySize)
	if _, err := io.ReadFull(r, key); err != nil {
		return nil, err
	}
	return c.NewPrivateKey(key)
}

func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) {
	if fips140only.Enforced() {
		return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
	}
	if len(key) != x25519PrivateKeySize {
		return nil, errors.New("crypto/ecdh: invalid private key size")
	}
	publicKey := make([]byte, x25519PublicKeySize)
	x25519Basepoint := [32]byte{9}
	x25519ScalarMult(publicKey, key, x25519Basepoint[:])
	// We don't check for the all-zero public key here because the scalar is
	// never zero because of clamping, and the basepoint is not the identity in
	// the prime-order subgroup(s).
	return &PrivateKey{
		curve:      c,
		privateKey: bytes.Clone(key),
		publicKey:  &PublicKey{curve: c, publicKey: publicKey},
	}, nil
}

func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) {
	if fips140only.Enforced() {
		return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode")
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Supply exactly 32 raw bytes: ensure len(key) == 32 before calling.
  2. Decode hex/base64 to raw bytes first (hex.DecodeString / base64.StdEncoding.DecodeString).
  3. Prefer GenerateKey(rand.Reader) to obtain correctly-sized keys rather than hand-constructing them.

Example fix

// before
priv, err := ecdh.X25519().NewPrivateKey([]byte(hexString)) // wrong length
// after
raw, _ := hex.DecodeString(hexString) // 32 bytes
priv, err := ecdh.X25519().NewPrivateKey(raw)
Defensive patterns

Strategy: validation

Validate before calling

func newX25519Priv(key []byte) (*ecdh.PrivateKey, error) {
    if len(key) != 32 {
        return nil, fmt.Errorf("X25519 private key must be 32 bytes, got %d", len(key))
    }
    return ecdh.X25519().NewPrivateKey(key)
}

Type guard

func isX25519PrivSize(key []byte) bool { return len(key) == 32 }

Prevention

When it happens

Trigger: Calling ecdh.X25519().NewPrivateKey(key) with len(key) != 32, e.g. a 31-byte key, a hex/base64 string instead of raw bytes, or a longer buffer.

Common situations: Passing a hex-encoded string instead of decoded bytes; truncated read; concatenating extra metadata into the key buffer; off-by-one slicing.

Related errors


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