{"record":{"id":"e9db0aa5f96f55e7","repo":"gchq/CyberChef","slug":"invalid-ciphertext-length-originallength-bytes","errorCode":null,"errorMessage":"Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.","messagePattern":"Invalid ciphertext length: (.+?) bytes\\. Must be a multiple of (.+?)\\.","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/RC6.mjs","lineNumber":555,"sourceCode":" * @param {number[]} cipherText - Ciphertext as byte array\n * @param {number[]} key - Key as byte array\n * @param {number[]} iv - IV (block size 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 * @param {number} rounds - Number of rounds (default: 20)\n * @param {number} w - Word size in bits (default: 32)\n * @returns {number[]} - Plaintext as byte array\n */\nexport function decryptRC6(cipherText, key, iv, mode = \"ECB\", padding = \"PKCS5\", rounds = 20, w = 32) {\n    const blockSize = getBlockSize(w);\n    const originalLength = cipherText.length;\n    if (originalLength === 0) return [];\n\n    const S = generateSubkeys(key, rounds, w);\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 ${blockSize}.`);\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, S, rounds, w));\n            }\n            break;\n\n        case \"CBC\": {\n            let ivBlock = [...iv];","sourceCodeStart":537,"sourceCodeEnd":573,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/RC6.mjs#L537-L573","documentation":"decryptRC6 rejects non-block-aligned ciphertext for ECB and CBC at RC6.mjs:555. These modes can only decrypt whole blocks (blockSize bytes, 16 for w=32), so any remainder signals corruption or a transport that altered the byte count. Stream modes (CFB/OFB/CTR) are zero-padded instead and skip this check.","triggerScenarios":"decryptRC6(cipherText, key, iv, mode='ECB'|'CBC', ...) with cipherText.length not a multiple of blockSize. Caused by truncated/corrupted ciphertext, base64/hex decode producing the wrong byte count, or decrypting ECB/CBC output that was originally a stream-mode result.","commonSituations":"Hex string with odd length decoded to N-1 bytes; base64 padding stripped before decode; ciphertext copied manually and a byte dropped; mode mismatch where CFB/CTR output is fed to an ECB/CBC decrypt.","solutions":["Re-verify the ciphertext byte count against blockSize (e.g. 16, 32, 48 ... for w=32).","Check the encoding/decoding step (hex/base64) that produced the byte array for off-by-one or truncation.","Confirm the mode matches what was used on encrypt — ECB/CBC require block-aligned input, stream modes do not.","If the data is genuinely short, switch the decrypt mode to a stream mode (CFB/OFB/CTR) consistent with the encrypt side."],"exampleFix":"// before: hex string had a char dropped, length now 31\nconst ct = fromHex(hexStr); // 31 bytes\nconst pt = decryptRC6(ct, key, iv, \"CBC\");\n// after: validate length up front\nif (ct.length % 16 !== 0) throw new Error(`ciphertext truncated: ${ct.length} bytes`);","handlingStrategy":"validation","validationCode":"import { getBlockSize } from \"./RC6Helpers.mjs\"; // or compute blockSize = w/8\nfunction assertBlockAligned(cipherText, w = 32) {\n  const blockSize = getBlockSize(w); // 16 for w=32\n  if (cipherText.length === 0) return;\n  if (cipherText.length % blockSize !== 0)\n    throw new TypeError(`ciphertext ${cipherText.length}B not a multiple of ${blockSize}`);\n}","typeGuard":"function isBlockAligned(bytes, blockSize) {\n  return Number.isInteger(bytes.length / blockSize) && bytes.length % blockSize === 0;\n}","tryCatchPattern":"try {\n  assertBlockAligned(ct, 16);\n  const pt = decryptRC6(ct, key, iv, \"CBC\");\n} catch (e) {\n  if (e instanceof OperationError && /Invalid ciphertext length/.test(e.message)) {\n    // re-derive ct from hex/base64 and retry once\n  } else throw e;\n}","preventionTips":["Validate ciphertext length is a multiple of blockSize before decrypting.","Re-derive byte arrays from hex/base64 through a single tested helper to avoid off-by-one truncation.","Match the decrypt mode to the encrypt mode — stream modes tolerate any length, ECB/CBC do not."],"tags":["rc6","cipher","block-mode","validation","decryption"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}