slackhq/nebula · error

key was not 32 bytes, is invalid ECDSA P256 private key

Error message

key was not 32 bytes, is invalid ECDSA P256 private key

What it means

The PEM block was recognized as an ECDSA P256 signing private key (ECDSAP256PrivateKeyBanner), but the decoded bytes are not exactly 32 bytes (the P-256 scalar size). The key body is truncated, padded, or malformed even though the banner is correct, so the library rejects it.

Source

Thrown at cert/pem.go:271

	k, r := pem.Decode(b)
	if k == nil {
		return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
	}
	var curve Curve
	switch k.Type {
	case EncryptedEd25519PrivateKeyBanner:
		return nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted
	case EncryptedECDSAP256PrivateKeyBanner:
		return nil, nil, Curve_P256, ErrPrivateKeyEncrypted
	case Ed25519PrivateKeyBanner:
		curve = Curve_CURVE25519
		if len(k.Bytes) != ed25519.PrivateKeySize {
			return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid Ed25519 private key", ed25519.PrivateKeySize)
		}
	case ECDSAP256PrivateKeyBanner:
		curve = Curve_P256
		if len(k.Bytes) != 32 {
			return nil, r, 0, fmt.Errorf("key was not 32 bytes, is invalid ECDSA P256 private key")
		}
	default:
		return nil, r, 0, fmt.Errorf("bytes did not contain a proper Ed25519/ECDSA private key banner")
	}
	return k.Bytes, r, curve, nil
}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Emit exactly the 32-byte raw scalar under the ECDSA P256 banner (use nebula-cert to generate)
  2. Strip DER/ASN.1 wrapping: extract the raw 32-byte private scalar before PEM-encoding
  3. Verify len(decoded bytes) == 32 before calling the API

Example fix

// before: DER-encoded key pasted raw
block.Bytes = derBytes // e.g. 121 bytes
// after
block.Bytes = rawScalar // exactly 32 bytes (d parameter)
Defensive patterns

Strategy: validation

Validate before calling

blk, _ := pem.Decode(data)
if blk != nil && blk.Type == "NEBULA ECDSA P256 SIGNING PRIVATE KEY" && len(blk.Bytes) != 32 {
    return fmt.Errorf("P256 signing key must be a raw 32-byte scalar, got %d bytes (DER-wrapped?)", len(blk.Bytes))
}

Type guard

func isValidP256SigningKey(b []byte) bool {
    blk, _ := pem.Decode(b)
    return blk != nil && blk.Type == "NEBULA ECDSA P256 SIGNING PRIVATE KEY" && len(blk.Bytes) == 32
}

Try / catch

key, _, _, err := nebula.UnmarshalSigningPrivateKeyFromPEM(raw)
if err != nil {
    return fmt.Errorf("P256 signing key body must be exactly 32 bytes: %w", err)
}

Prevention

When it happens

Trigger: Call UnmarshalSigningPrivateKeyFromPEM with a block of type ECDSAP256PrivateKeyBanner whose k.Bytes length != 32 — e.g. DER-wrapped key material, a truncated scalar, or extra bytes appended.

Common situations: Exporting a P256 key from another tool in SEC1/PKCS#8 DER form (longer than 32 bytes) and pasting it under the nebula banner, or copy/paste truncation of the base64 body.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/03c20ef6c2acbd35. Report an issue: GitHub.