{"record":{"id":"831f35f56dae0b4a","repo":"gchq/CyberChef","slug":"invalid-ciphertext-length-originallength-bytes-831f35","errorCode":null,"errorMessage":"Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${BLOCK_SIZE}.","messagePattern":"Invalid ciphertext length: (.+?) bytes\\. Must be a multiple of (.+?)\\.","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/TEA.mjs","lineNumber":362,"sourceCode":"/**\n * Decrypt with block cipher modes\n *\n * @param {number[]} cipherText - Ciphertext bytes\n * @param {number[]} key - 16-byte key\n * @param {number[]} iv - 8-byte IV (ignored for ECB)\n * @param {string} mode - \"ECB\", \"CBC\", \"CFB\", \"OFB\", \"CTR\"\n * @param {string} padding - \"PKCS5\", \"NO\", \"ZERO\", \"RANDOM\", \"BIT\"\n * @param {Function} encryptBlockFn - Block encrypt function (used for stream modes)\n * @param {Function} decryptBlockFn - Block decrypt function (used for ECB/CBC)\n * @returns {number[]} - Plaintext bytes\n */\nfunction decryptWithMode(cipherText, key, iv, mode, padding, encryptBlockFn, decryptBlockFn) {\n    const originalLength = cipherText.length;\n    if (originalLength === 0) return [];\n\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        if ((originalLength % BLOCK_SIZE) !== 0)\n            throw new OperationError(\n                `Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${BLOCK_SIZE}.`\n            );\n    } else {\n        while ((cipherText.length % BLOCK_SIZE) !== 0)\n            cipherText.push(0);\n    }\n\n    const plainText = [];\n\n    switch (mode) {\n        case \"ECB\":\n            for (let i = 0; i < cipherText.length; i += BLOCK_SIZE) {\n                plainText.push(...decryptBlockFn(cipherText.slice(i, i + BLOCK_SIZE), key));\n            }\n            break;\n\n        case \"CBC\": {\n            let ivBlock = [...iv];","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/TEA.mjs#L344-L380","documentation":"Thrown by decryptWithMode() in TEA.mjs when decrypting in ECB or CBC mode and the ciphertext byte length is not an exact multiple of the 8-byte TEA block size. ECB/CBC are block modes that cannot stream, so any remainder indicates truncation, corruption, or that the data was produced with a different cipher/mode. Stream modes (CFB/OFB/CTR) instead zero-pad internally and slice back to original length, so they never hit this check.","triggerScenarios":"Calling decryptTEA()/decryptXTEA() with mode \"ECB\" or \"CBC\" and a ciphertext whose .length % 8 !== 0. Typical causes: truncating base64/hex decode output, feeding a ciphertext encrypted with a stream mode but declaring ECB/CBC on decrypt, off-by-one slicing of an ArrayBuffer, or concatenating two partial ciphertext buffers.","commonSituations":"Key/mode mismatch between encrypt and decrypt sides; wrong endianness or wrong byte offset when extracting ciphertext from a larger protocol frame; copy-paste dropping the final block; hex string with odd number of characters decoding to a non-multiple length.","solutions":["Verify the ciphertext was encrypted with ECB/CBC and a TEA-family 8-byte block; if it was a stream mode, decrypt with the matching mode (CFB/OFB/CTR).","Check the byte length: console.log(cipherText.length, cipherText.length % 8). Re-derive the bytes from the source (re-decode hex/base64) to rule out truncation.","If the source is genuinely not block-aligned, it is not valid TEA ECB/CBC ciphertext — do not pad it yourself; locate where the data was truncated."],"exampleFix":"// before: ciphertext is 13 bytes, mode declared as CBC\ndecryptTEA(cipherText, key, iv, \"CBC\", \"PKCS5\"); // throws\n// after: it was actually produced with CTR\ndecryptTEA(cipherText, key, iv, \"CTR\", \"PKCS5\");","handlingStrategy":"validation","validationCode":"const BLOCK_SIZE = 8; // TEA\nif ((mode === \"ECB\" || mode === \"CBC\") && cipherText.length % BLOCK_SIZE !== 0) {\n    throw new Error(\n        `Ciphertext is ${cipherText.length} bytes; ECB/CBC require a multiple of ${BLOCK_SIZE}. ` +\n        `Check for truncation or use the matching stream mode.`\n    );\n}\nplainText = decryptTEA(cipherText, key, iv, mode, padding);","typeGuard":"function isBlockAligned(bytes, blockSize = 8) {\n    return Array.isArray(bytes) && bytes.length % blockSize === 0;\n}","tryCatchPattern":"try {\n    plainText = decryptTEA(cipherText, key, iv, mode, padding);\n} catch (e) {\n    if (e instanceof OperationError && /Invalid ciphertext length/.test(e.message)) {\n        return { error: `Ciphertext length is invalid for ${mode}. Verify the data was encrypted with this cipher and mode.` };\n    }\n    throw e;\n}","preventionTips":["Always decrypt with the same mode used to encrypt; store the mode alongside the ciphertext.","Re-derive bytes from hex/base64 immediately before decrypting rather than passing slices of larger buffers.","When reading ciphertext from a frame/protocol, use the explicit length field to slice exactly the right bytes."],"tags":["crypto","tea","decryption","block-alignment","argument-validation"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}