slackhq/nebula · error

key was not %d bytes, is invalid %s private key

Error message

key was not %d bytes, is invalid %s private key

What it means

The PEM block had a valid nebula private key banner, but the decoded key bytes are not exactly 32 bytes, which both supported curves (X25519 and P256) require. The key body was truncated, extended, or otherwise corrupted after the banner was written. The library rejects it rather than deriving a broken key.

Source

Thrown at cert/pem.go:247

func UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
	k, r := pem.Decode(b)
	if k == nil {
		return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
	}
	var expectedLen int
	var curve Curve
	switch k.Type {
	case X25519PrivateKeyBanner:
		expectedLen = 32
		curve = Curve_CURVE25519
	case P256PrivateKeyBanner:
		expectedLen = 32
		curve = Curve_P256
	default:
		return nil, r, 0, fmt.Errorf("bytes did not contain a proper private key banner")
	}
	if len(k.Bytes) != expectedLen {
		return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s private key", expectedLen, curve)
	}
	return k.Bytes, r, curve, nil
}

func UnmarshalSigningPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
	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 {

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Regenerate the host private key with nebula-cert keygen and redeploy it
  2. Decode the PEM body and assert it is exactly 32 bytes before calling the API
  3. Re-transfer the key file intact (compare checksums); avoid manual copy/paste of key material

Example fix

// before
block.Bytes = key[:20] // truncated
// after
if len(key) != 32 { return errors.New("bad key") }
block.Bytes = key
Defensive patterns

Strategy: validation

Validate before calling

blk, _ := pem.Decode(data)
if blk != nil && len(blk.Bytes) != 32 {
    return fmt.Errorf("host private key must decode to 32 bytes, got %d; regenerate the key", len(blk.Bytes))
}

Type guard

func hasValidHostKeyLength(b []byte) bool {
    blk, _ := pem.Decode(b)
    return blk != nil && len(blk.Bytes) == 32
}

Try / catch

key, _, _, err := nebula.UnmarshalPrivateKeyFromPEM(raw)
if err != nil {
    return fmt.Errorf("host private key body is the wrong size; regenerate with nebula-cert keygen: %w", err)
}

Prevention

When it happens

Trigger: Call UnmarshalPrivateKeyFromPEM with a block whose Type is X25519PrivateKeyBanner or P256PrivateKeyBanner but whose base64 body decodes to len(k.Bytes) != 32 — e.g. a half-pasted key, a key with extra bytes appended, or raw-key-size mismatch from manual encoding.

Common situations: Copy/paste truncation in editors or chat tools, secrets managers that mangled base64, hand-crafted PEM blocks from raw key material with wrong byte length, or converting from another tool's format incorrectly.

Related errors


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