slackhq/nebula · error
invalid ciphertext blob - blob shorter than nonce length
Error message
invalid ciphertext blob - blob shorter than nonce length
What it means
splitNonceCiphertext requires the encrypted blob to be strictly longer than the nonce size, because valid AES-GCM output is nonce||ciphertext. A blob at or below nonceSize cannot contain any ciphertext, so aes256Decrypt rejects it as malformed rather than attempting to slice it.
Source
Thrown at cert/crypto.go:153
return nil, fmt.Errorf("salt must be set in argon2Parameters")
} else if len(params.salt) < 16 {
return nil, fmt.Errorf("salt must be at least 128 bits")
}
key := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)
return key, nil
}
// Prepends nonce to ciphertext
func joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {
return append(nonce, ciphertext...)
}
// Splits nonce from ciphertext
func splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {
if len(blob) <= nonceSize {
return nil, nil, fmt.Errorf("invalid ciphertext blob - blob shorter than nonce length")
}
return blob[:nonceSize], blob[nonceSize:], nil
}
// EncryptAndMarshalSigningPrivateKey is a simple helper to encrypt and PEM encode a private key
func EncryptAndMarshalSigningPrivateKey(curve Curve, b []byte, passphrase []byte, kdfParams *Argon2Parameters) ([]byte, error) {
ciphertext, err := aes256Encrypt(passphrase, kdfParams, b)
if err != nil {
return nil, err
}
b, err = proto.Marshal(&RawNebulaEncryptedData{
EncryptionMetadata: &RawNebulaEncryptionMetadata{
EncryptionAlgorithm: "AES-256-GCM",
Argon2Parameters: &RawNebulaArgon2Parameters{
Version: kdfParams.version,
Memory: kdfParams.Memory,View on GitHub (pinned to dd8f660c0a)
Solutions
- Verify the input is genuinely Nebula-encrypted data: decode the PEM block and confirm its Type matches an Encrypted*PrivateKeyBanner and its length exceeds the nonce size (12 bytes for GCM)
- Re-export/regenerate the encrypted key file - the existing blob is truncated or not encrypted data
- Confirm you are decrypting the right file and not a plaintext key or public certificate
Example fix
// before
block, _ := pem.Decode(data)
key, err := cert.DecryptAndUnmarshalSigningPrivateKey(pass, block.Bytes) // blob too short
// after
block, _ := pem.Decode(data)
if block == nil || len(block.Bytes) <= 12 {
return fmt.Errorf("not encrypted nebula key data")
}
key, err := cert.DecryptAndUnmarshalSigningPrivateKey(pass, block.Bytes) Defensive patterns
Strategy: validation
Validate before calling
if len(blob) <= 12 { return fmt.Errorf("blob too short to contain nonce+ciphertext: %d", len(blob)) } Type guard
func isPlausibleEncryptedBlob(b []byte) bool { return len(b) > 12 } Try / catch
key, err := cert.DecryptAndUnmarshalSigningPrivateKey(pass, blob)
if err != nil {
if strings.Contains(err.Error(), "invalid ciphertext blob") { /* wrong/truncated input */ }
return err
} Prevention
- Check pem.Decode returned a non-nil block before using block.Bytes
- Confirm the PEM block Type is an Encrypted*PrivateKeyBanner
- Verify file size/integrity after transfer before decrypting
When it happens
Trigger: Calling aes256Decrypt (via DecryptAndUnmarshalSigningPrivateKey) with a blob whose length <= nonceSize (12 bytes for AES-GCM): an empty blob, a bare key with no encryption wrapper, or corrupted/truncated PEM-decoded data.
Common situations: Attempting to decrypt a key file that was never encrypted; passing a PEM block that is actually a plaintext key or certificate; file truncation during transfer; decrypting with the wrong format/version of encrypted data.
Related errors
- use of Curve25519 is not allowed in FIPS 140-only mode
- use of Curve25519 is not allowed in FIPS 140-only mode
- use of Curve25519 is not allowed in FIPS 140-only mode
- ErrAlreadySeen
- pki: use of Curve25519 is not allowed in FIPS 140-only mode
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/be318cb3b635c5fc.
Report an issue: GitHub.