Error reference

Ciphertext is malformed (expected iv.ciphertext.tag)

2 min read

A structural failure, thrown before any cryptography runs. decryptWithKey splits the payload on . and requires exactly three parts.

The format

base64url(iv) . base64url(ciphertext) . base64url(tag)

Three segments. The initialisation vector is 12 bytes — the 96-bit nonce GCM is specified around, not a truncated 16 — and the authentication tag is 16.

What causes it

Something re-encoded the payload. A JSON round-trip that decoded and re-encoded base64, or a transport that treated . as a delimiter and split on it.

Truncation. A field with a length limit that cut the payload short, leaving two segments instead of three.

Wrong field. Passing a plaintext, a key, or a different column's value into the decrypt path. This is the most common one in practice, and it fails here rather than at the crypto layer, which is a small mercy — the error names the expected shape.

Version skew. A payload written by a client using a different wire format.

How to fix it

Look at the raw stored value before assuming a crypto problem. If it does not contain exactly two dots, nothing is wrong with the key or the algorithm and the bug is upstream in whatever wrote or moved the value.

A payload that is well-formed but fails to decrypt produces a different error, and the distinction matters for where you look.

Checking the value directly

Before debugging keys or algorithms, look at what is actually stored:

SELECT length(ciphertext), ciphertext LIKE '%.%.%' FROM ...;

A value that does not contain exactly two dots cannot reach the cipher, and no amount of key rotation will change that.

Why the check is separate from decryption

Splitting the structural check out from the cryptographic one means a malformed payload produces an error that names the expected shape, rather than a generic decryption failure that sends you looking at keys.

That distinction is worth preserving whenever you validate a wire format: the error should tell you which layer rejected the input. A single "decryption failed" for both cases would collapse two very different bugs — one in whatever wrote the value, one in key management — into the same message.