gchq/CyberChef · error · OperationError

With ECB or CBC modes, the input must be divisible into 16 b

Error message

With ECB or CBC modes, the input must be divisible into 16 byte blocks. (${cipherText.length & 0xF} bytes extra)

What it means

decryptSM4 at SM4.mjs:259 enforces block alignment for ECB/CBC unless ignorePadding is set. SM4's block size is 16 bytes; a ciphertext whose length is not a multiple of 16 cannot be the output of a correct ECB/CBC encrypt and is rejected.

Source

Thrown at src/core/lib/SM4.mjs:259

 *
 * @param {byteArray} cipherText - The ciphertext
 * @param {byteArray} key - The cipher key, 16 bytes.
 * @param {byteArray} iv - The IV or nonce, 16 bytes (not used with ECB mode)
 * @param {string} mode - The block cipher mode "CBC", "ECB", "CFB", "OFB", "CTR"
 * @param {boolean] ignorePadding - If true, ignore padding issues in ECB/CBC mode.
 * @returns {byteArray} - The cipher text.
 */
export function decryptSM4(cipherText, key, iv, mode="ECB", ignorePadding=false) {
    const originalLength = cipherText.length;
    if (originalLength === 0)
        return [];
    let roundKey = initSM4RoundKey(bytesToInts(key, 0));

    if (mode === "ECB" || mode === "CBC") {
        /* Init decryption key */
        roundKey = roundKey.reverse();
        if ((originalLength & 0xF) !== 0 && !ignorePadding)
            throw new OperationError(`With ECB or CBC modes, the input must be divisible into 16 byte blocks. (${cipherText.length & 0xF} bytes extra)`);
    } else { /* Pad dummy bytes for other modes, chop them off at the end */
        while ((cipherText.length & 0xF) !== 0)
            cipherText.push(0);
    }

    const clearText = [];
    switch (mode) {
        case "ECB":
            for (let i = 0; i < cipherText.length; i += BLOCKSIZE)
                Array.prototype.push.apply(clearText, intsToBytes(encryptBlockSM4(bytesToInts(cipherText, i), roundKey)));
            break;
        case "CBC":
            iv = bytesToInts(iv, 0);
            for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
                const block = encryptBlockSM4(bytesToInts(cipherText, i), roundKey);
                block[0] ^= iv[0]; block[1] ^= iv[1];
                block[2] ^= iv[2]; block[3] ^= iv[3];
                Array.prototype.push.apply(clearText, intsToBytes(block));

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Verify the ciphertext byte length is a multiple of 16 before calling decryptSM4.
  2. Re-derive the byte array from its hex/base64 encoding and check for truncation.
  3. Confirm the decrypt mode matches the encrypt mode.
  4. If you knowingly want to skip alignment/padding checks, pass ignorePadding=true (but the result will be unreliable for mis-sized input).

Example fix

// before
const pt = decryptSM4(ct, key, iv, "CBC"); // ct.length === 17
// after
if (ct.length & 0xF) throw new Error(`ciphertext not block-aligned: ${ct.length} bytes`);
const pt = decryptSM4(ct, key, iv, "CBC");
Defensive patterns

Strategy: validation

Validate before calling

function assertSm4CipherAligned(cipherText, ignorePadding, mode) {
  if (!ignorePadding && (mode === "ECB" || mode === "CBC")) {
    if (cipherText.length & 0xF)
      throw new TypeError(
        `ECB/CBC ciphertext must be a 16-byte multiple; got ${cipherText.length} bytes`);
  }
}

Type guard

function isSm4CipherBlockAligned(bytes) {
  return Number.isInteger(bytes.length) && (bytes.length & 0xF) === 0;
}

Try / catch

import OperationError from "../errors/OperationError.mjs";
try {
  assertSm4CipherAligned(ct, ignorePadding, mode);
  const pt = decryptSM4(ct, key, iv, mode, ignorePadding);
} catch (e) {
  if (e instanceof OperationError && /divisible into 16 byte blocks/.test(e.message)) {
    // re-derive ct from hex/base64 and check for truncation
  } else throw e;
}

Prevention

When it happens

Trigger: decryptSM4(cipherText, key, iv, mode='ECB'|'CBC', ignorePadding=false) with (cipherText.length & 0xF) !== 0. Caused by truncated/corrupted ciphertext, encoding/decoding error in the hex/base64 path, or feeding a stream-mode ciphertext to an ECB/CBC decryptor.

Common situations: Hex string of odd length producing a short byte array; base64 padding lost in transit; mode mismatch where CFB/CTR output is decrypted as ECB/CBC; manual copy of ciphertext dropping a byte.

Related errors


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