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

  1. Verify the senderPublicKey passed to Decrypt is exactly the base64 public key of the party that encrypted the payload.
  2. Confirm the Encrypter's privateKey corresponds to the recipient public key used at encryption time; re-check which key file/env value is loaded.
  3. 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.
  4. 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

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


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/4e01b6647117a8dc. Report an issue: GitHub.