{"record":{"id":"b073aacf359720bf","repo":"gchq/CyberChef","slug":"invalid-ciphertext-length-originallength-bytes-b073aa","errorCode":null,"errorMessage":"Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of 16.","messagePattern":"Invalid ciphertext length: (.+?) bytes\\. Must be a multiple of 16\\.","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/Twofish.mjs","lineNumber":538,"sourceCode":"/**\n * Decrypt using Twofish cipher with specified block mode\n *\n * @param {number[]} cipherText - Ciphertext as byte array\n * @param {number[]} key - Key (16, 24, or 32 bytes)\n * @param {number[]} iv - IV (16 bytes, not used for ECB)\n * @param {string} mode - Block cipher mode (\"ECB\", \"CBC\", \"CFB\", \"OFB\", \"CTR\")\n * @param {string} padding - Padding type (\"NO\", \"PKCS5\", \"ZERO\", \"RANDOM\", \"BIT\")\n * @returns {number[]} - Plaintext as byte array\n */\nexport function decryptTwofish(cipherText, key, iv, mode = \"ECB\", padding = \"PKCS5\") {\n    const originalLength = cipherText.length;\n    if (originalLength === 0) return [];\n\n    const keyData = generateSubkeys(key);\n\n    if (mode === \"ECB\" || mode === \"CBC\") {\n        if ((originalLength % BLOCKSIZE) !== 0)\n            throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of 16.`);\n    } else {\n        // Pad for stream modes\n        while ((cipherText.length % BLOCKSIZE) !== 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 += BLOCKSIZE) {\n                const block = cipherText.slice(i, i + BLOCKSIZE);\n                plainText.push(...decryptBlock(block, keyData));\n            }\n            break;\n\n        case \"CBC\": {\n            let ivBlock = [...iv];","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/Twofish.mjs#L520-L556","documentation":"Thrown by decryptTwofish() in Twofish.mjs when decrypting in ECB or CBC and the ciphertext length is not a multiple of the 16-byte Twofish block size. Block modes cannot operate on partial blocks, so a non-aligned length signals truncation, corruption, or a mode/cipher mismatch. Stream modes (CFB/OFB/CTR) zero-pad internally and slice back, so they never reach this check.","triggerScenarios":"Calling decryptTwofish() with mode \"ECB\" or \"CBC\" and cipherText.length % 16 !== 0. Typical: truncated base64/hex decode, mode mismatch (data was CTR-encrypted), wrong cipher (data is TEA/AES not Twofish), or a copy-paste that dropped trailing bytes.","commonSituations":"Encrypt/decrypt mode disagreement; hex string with odd character count; ArrayBuffer slicing with the wrong byteOffset/length; interop with a tool that stripped padding before storing ciphertext.","solutions":["Verify cipherText.length % 16 === 0; re-derive the bytes from the source (re-decode hex/base64) to rule out truncation.","If the data was encrypted with a stream mode (CFB/OFB/CTR), decrypt with the matching mode instead of ECB/CBC.","If the data is genuinely not block-aligned it is not valid Twofish ECB/CBC ciphertext — locate where it was truncated rather than padding it yourself."],"exampleFix":"// before: 30-byte ciphertext declared as CBC\ndecryptTwofish(ct, key, iv, \"CBC\", \"PKCS5\"); // throws\n// after: data was CTR-encrypted\ndecryptTwofish(ct, key, iv, \"CTR\", \"PKCS5\");","handlingStrategy":"validation","validationCode":"const BLOCK = 16; // Twofish\nif ((mode === \"ECB\" || mode === \"CBC\") && cipherText.length % BLOCK !== 0) {\n    throw new Error(\n        `Ciphertext is ${cipherText.length} bytes; ECB/CBC require a multiple of ${BLOCK}. ` +\n        `Check for truncation or use the matching stream mode.`\n    );\n}\nplainText = decryptTwofish(cipherText, key, iv, mode, padding);","typeGuard":"function isBlockAligned(bytes, blockSize = 16) {\n    return Array.isArray(bytes) && bytes.length % blockSize === 0;\n}","tryCatchPattern":"try {\n    pt = decryptTwofish(ct, key, iv, mode, padding);\n} catch (e) {\n    if (e instanceof OperationError && /Invalid ciphertext length/.test(e.message)) {\n        return { error: `Ciphertext length invalid for ${mode}. Verify cipher/mode and re-derive bytes.` };\n    }\n    throw e;\n}","preventionTips":["Decrypt with the same mode (and cipher) used to encrypt.","Slice ciphertext by explicit length fields, not by buffer boundaries.","Re-decode hex/base64 at the point of use to avoid stale/truncated slices."],"tags":["crypto","twofish","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"}