slackhq/nebula · error
key was not %d bytes, is invalid %s public key
Error message
key was not %d bytes, is invalid %s public key
What it means
The PEM banner was valid but the block body length does not match the expected raw key size: 32 bytes for X25519 (CURVE25519) or 65 bytes for an uncompressed P256 public key. The parsed Curve name is interpolated into the message so you can see which key type was expected.
Source
Thrown at cert/pem.go:173
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 X25519PublicKeyBanner:
expectedLen = 32
curve = Curve_CURVE25519
case P256PublicKeyBanner:
// Uncompressed
expectedLen = 65
curve = Curve_P256
default:
return nil, r, 0, fmt.Errorf("bytes did not contain a proper public key banner")
}
if len(k.Bytes) != expectedLen {
return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s public key", expectedLen, curve)
}
return k.Bytes, r, curve, nil
}
// UnmarshalSigningPublicKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
// consumed data or an error on failure. Only Ed25519/ECDSA public key banners are accepted.
// Use UnmarshalPublicKeyFromPEM for X25519/P256 (ECDH) banners.
func UnmarshalSigningPublicKeyFromPEM(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 Ed25519PublicKeyBanner:
expectedLen = 32
curve = Curve_CURVE25519View on GitHub (pinned to dd8f660c0a)
Solutions
- For P256 keys, ensure the point is in uncompressed form (0x04 prefix + 32-byte X + 32-byte Y = 65 bytes); re-export with uncompressed encoding
- For X25519 keys, ensure exactly 32 raw key bytes are in the PEM body
- Re-emit the public key via the library's Marshal path (e.g. certificate PublicKey marshalling) rather than hand-building the PEM
Example fix
// before blk.Bytes = elliptic.MarshalCompressed(curve, x, y) // 33 bytes for P256 // after blk.Bytes = elliptic.Marshal(curve, x, y) // 65 bytes, uncompressed
Defensive patterns
Strategy: validation
Validate before calling
func checkECDHKeyLen(b []byte) error {
blk, _ := pem.Decode(b)
if blk == nil {
return fmt.Errorf("no PEM")
}
switch blk.Type {
case cert.X25519PublicKeyBanner:
if len(blk.Bytes) != 32 { return fmt.Errorf("X25519 key must be 32 bytes, got %d", len(blk.Bytes)) }
case cert.P256PublicKeyBanner:
if len(blk.Bytes) != 65 || blk.Bytes[0] != 0x04 { return fmt.Errorf("P256 key must be 65-byte uncompressed point") }
}
return nil
} Try / catch
pub, rest, curve, err := cert.UnmarshalPublicKeyFromPEM(b)
if err != nil {
if strings.Contains(err.Error(), "invalid") && strings.Contains(err.Error(), "public key") {
return fmt.Errorf("key body length does not match banner; re-export the key in the expected encoding: %w", err)
}
return err
} Prevention
- Export P256 public keys uncompressed (0x04-prefixed, 65 bytes), never compressed
- X25519 public keys are always exactly 32 bytes; verify before PEM-encoding
- Prefer the library's own marshal functions over hand-building PEM blocks
When it happens
Trigger: Calling UnmarshalPublicKeyFromPEM with a correctly-bannered block whose k.Bytes is the wrong length — e.g. compressed (33-byte) P256 point, truncated body, extra padding inside the block, or a point-encoded P256 key from another tool.
Common situations: Exporting P256 public keys from OpenSSL in compressed form (33 bytes) instead of uncompressed (65 bytes), copy/paste truncating base64, or hand-assembling PEM blocks with wrong DER/bytes content.
Related errors
- input did not contain a valid PEM encoded block
- bytes did not contain a proper public key banner
- bytes did not contain a proper Ed25519/ECDSA public key bann
- ErrTruncatedPEMBlock
- input did not contain a valid PEM encoded block
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/9614bf901cd9144f.
Report an issue: GitHub.