gchq/CyberChef · error · OperationError

Invalid ciphertext length: ${originalLength} bytes. Must be

Error message

Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.

What it means

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.

Source

Thrown at src/core/lib/RC6.mjs:555

 * @param {number[]} cipherText - Ciphertext as byte array
 * @param {number[]} key - Key as byte array
 * @param {number[]} iv - IV (block size bytes, not used for ECB)
 * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
 * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
 * @param {number} rounds - Number of rounds (default: 20)
 * @param {number} w - Word size in bits (default: 32)
 * @returns {number[]} - Plaintext as byte array
 */
export function decryptRC6(cipherText, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
    const blockSize = getBlockSize(w);
    const originalLength = cipherText.length;
    if (originalLength === 0) return [];

    const S = generateSubkeys(key, rounds, w);

    if (mode === "ECB" || mode === "CBC") {
        if ((originalLength % blockSize) !== 0)
            throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.`);
    } else {
        // Pad for stream modes
        while ((cipherText.length % blockSize) !== 0)
            cipherText.push(0);
    }

    const plainText = [];

    switch (mode) {
        case "ECB":
            for (let i = 0; i < cipherText.length; i += blockSize) {
                const block = cipherText.slice(i, i + blockSize);
                plainText.push(...decryptBlock(block, S, rounds, w));
            }
            break;

        case "CBC": {
            let ivBlock = [...iv];

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Re-verify the ciphertext byte count against blockSize (e.g. 16, 32, 48 ... for w=32).
  2. Check the encoding/decoding step (hex/base64) that produced the byte array for off-by-one or truncation.
  3. Confirm the mode matches what was used on encrypt — ECB/CBC require block-aligned input, stream modes do not.
  4. If the data is genuinely short, switch the decrypt mode to a stream mode (CFB/OFB/CTR) consistent with the encrypt side.

Example fix

// before: hex string had a char dropped, length now 31
const ct = fromHex(hexStr); // 31 bytes
const pt = decryptRC6(ct, key, iv, "CBC");
// after: validate length up front
if (ct.length % 16 !== 0) throw new Error(`ciphertext truncated: ${ct.length} bytes`);
Defensive patterns

Strategy: validation

Validate before calling

import { getBlockSize } from "./RC6Helpers.mjs"; // or compute blockSize = w/8
function assertBlockAligned(cipherText, w = 32) {
  const blockSize = getBlockSize(w); // 16 for w=32
  if (cipherText.length === 0) return;
  if (cipherText.length % blockSize !== 0)
    throw new TypeError(`ciphertext ${cipherText.length}B not a multiple of ${blockSize}`);
}

Type guard

function isBlockAligned(bytes, blockSize) {
  return Number.isInteger(bytes.length / blockSize) && bytes.length % blockSize === 0;
}

Try / catch

try {
  assertBlockAligned(ct, 16);
  const pt = decryptRC6(ct, key, iv, "CBC");
} catch (e) {
  if (e instanceof OperationError && /Invalid ciphertext length/.test(e.message)) {
    // re-derive ct from hex/base64 and retry once
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13). Data as JSON: /api/errors/e9db0aa5f96f55e7. Report an issue: GitHub.