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
- 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.
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
- 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.
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
- With ECB or CBC modes, the input must be divisible into 16 b
- Invalid block cipher mode: ${mode}
- No padding requested in ${mode} mode but input is not a 16-b
- No padding requested but input length (${message.length} byt
- Invalid block cipher mode: ${mode}
AI-assisted analysis of gchq/CyberChef@4290ea7539 (2026-08-13).
Data as JSON: /api/errors/e9db0aa5f96f55e7.
Report an issue: GitHub.