cloudflare/cloudflared · error
failed to decrypt message
Error message
failed to decrypt message
What it means
Decrypt uses NaCl box (XSalsa20-Poly1305) to decrypt a payload with the sender's public key and the local private key. box.Open returns ok=false when authentication fails, meaning the ciphertext, nonce, or key pair does not match what was used to encrypt. The library converts that failure into this generic error rather than leaking crypto internals.
Source
Thrown at token/encrypt.go:82
// Decrypt data that was encrypted using our publicKey. It will use our privateKey and the sender's publicKey to decrypt
// data is an encrypted buffer of data, mostly like from the Encrypt function. Messages contain the nonce data on the front
// of the message.
// senderPublicKey is a base64 encoded version of the sender's public key (most likely from the PublicKey function).
// The return value is the decrypted buffer or an error.
func (e *Encrypter) Decrypt(data []byte, senderPublicKey string) ([]byte, error) {
if len(data) < 24 {
return nil, errors.New("message is too short to contain a nonce")
}
var decryptNonce [24]byte
copy(decryptNonce[:], data[:24]) // we pull the nonce from the front of the actual message.
pubKey, err := e.decodePublicKey(senderPublicKey)
if err != nil {
return nil, err
}
decrypted, ok := box.Open(nil, data[24:], &decryptNonce, pubKey, e.privateKey)
if !ok {
return nil, errors.New("failed to decrypt message")
}
return decrypted, nil
}
// decodePublicKey will base64 decode the provided key to the box representation
func (e *Encrypter) decodePublicKey(key string) (*[32]byte, error) {
pub, err := base64.URLEncoding.DecodeString(key)
if err != nil {
return nil, err
}
var newKey [32]byte
copy(newKey[:], pub)
return &newKey, nil
}
View on GitHub (pinned to 2253eeeb25)
Solutions
- Verify the senderPublicKey passed to Decrypt is exactly the base64 public key of the party that encrypted the payload.
- Confirm the Encrypter's privateKey corresponds to the recipient public key used at encryption time; re-check which key file/env value is loaded.
- Ensure the full raw output of Encrypt (nonce prefix + ciphertext) is passed to Decrypt unmodified; check encoding/decoding steps (base64, JSON, HTTP bodies) for corruption or trimming.
- Regenerate a fresh keypair pair and do a round-trip test (Encrypt then Decrypt with the new pair) to isolate stale-key issues.
Example fix
// before: using a mismatched key from config decrypted, err := e.Decrypt(cfg.OldServicePublicKey, payload) // after: use the public key of the actual sender decrypted, err := e.Decrypt(senderKey.Public(), payload)
Defensive patterns
Strategy: try-catch
Validate before calling
if senderPublicKey == "" || len(payload) < 24+box.Overhead {
return fmt.Errorf("payload too short or sender public key missing")
} Type guard
func canDecrypt(pub *[32]byte, payload []byte) bool {
return pub != nil && len(payload) >= 24+box.Overhead
} Try / catch
decrypted, err := e.Decrypt(senderPublicKey, payload)
if err != nil {
if err.Error() == "failed to decrypt message" {
// log sender key fingerprint + payload hash, re-fetch keys and retry once
}
return fmt.Errorf("decrypting token payload: %w", err)
} Prevention
- Store and pass sender keys together with the encrypted payloads they correspond to (keyed envelopes).
- Use base64 (not raw bytes) for all transport of nonce+ciphertext and validate length before decrypting.
- Round-trip test every keypair rotation: encrypt with new, decrypt with new, before retiring old keys.
- Log a hash of the payload and key fingerprint on failure to spot which side is mismatched.
When it happens
Trigger: Calling Encrypter.Decrypt(senderPublicKey, ciphertext) with data whose last segment fails Poly1305 verification: ciphertext encrypted with a different sender key, a corrupted or truncated payload, the wrong private key loaded, or the first 24-byte nonce prefix split incorrectly.
Common situations: Base64 keys copied from the wrong token or service (sender/public key mismatch), payloads mangled by passing through a non-binary-safe channel (e.g. trimming base64, CRLF translation), rotating keypairs on one side only, or decrypting a payload that was encrypted with a different version of the token format.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- message is too short to contain a nonce
- empty application token
- failed to verify token
- Decoded tunnel secret must be at least 32 bytes long
- aud array contains non-string elements
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/4e01b6647117a8dc.
Report an issue: GitHub.