gchq/CyberChef · error · OperationError

Invalid PKCS#5 padding.

Error message

Invalid PKCS#5 padding.

What it means

TEA's removePadding PKCS#5 inner-loop check at TEA.mjs:239. After the trailing byte passes the range check (1..BLOCK_SIZE), the code verifies each of the trailing padByte bytes equals padByte; any mismatch means the pad is malformed.

Source

Thrown at src/core/lib/TEA.mjs:239

 * @param {number[]} message
 * @param {string} padding
 * @returns {number[]}
 */
function removePadding(message, padding) {
    if (message.length === 0) return message;

    switch (padding) {
        case "NO":
        case "ZERO":
        case "RANDOM":
            return message;

        case "PKCS5": {
            const padByte = message[message.length - 1];
            if (padByte > 0 && padByte <= BLOCK_SIZE) {
                for (let i = 0; i < padByte; i++) {
                    if (message[message.length - 1 - i] !== padByte) {
                        throw new OperationError("Invalid PKCS#5 padding.");
                    }
                }
                return message.slice(0, message.length - padByte);
            }
            throw new OperationError("Invalid PKCS#5 padding.");
        }

        case "BIT": {
            for (let i = message.length - 1; i >= 0; i--) {
                if (message[i] === 0x80) return message.slice(0, i);
                if (message[i] !== 0) throw new OperationError("Invalid BIT padding.");
            }
            throw new OperationError("Invalid BIT padding.");
        }

        default:
            throw new OperationError(`Unknown padding type: ${padding}`);
    }

View on GitHub (pinned to 4290ea7539)

Solutions

  1. Verify key, IV, and mode match the encrypt side.
  2. Confirm the encrypt-time padding was 'PKCS5'; if it was NO/ZERO/RANDOM/BIT, decrypt with that matching value.
  3. As a diagnostic, decrypt with padding='NO' and inspect the raw tail bytes.

Example fix

// before
const pt = decryptWithBlockMode(ct, key, iv, "CBC", "PKCS5");
// after: data was ZERO-padded
const pt = decryptWithBlockMode(ct, key, iv, "CBC", "ZERO");
Defensive patterns

Strategy: validation

Validate before calling

const TEA_BLOCK_SIZE = 8;
function looksLikeValidPkcs5Pad(plain) {
  if (plain.length === 0) return false;
  const padByte = plain[plain.length - 1];
  if (padByte < 1 || padByte > TEA_BLOCK_SIZE) return false;
  for (let i = 0; i < padByte; i++)
    if (plain[plain.length - 1 - i] !== padByte) return false;
  return true;
}

Type guard

function isConsistentPkcs5Pad(plain, blockSize = 8) {
  const padByte = plain.length ? plain[plain.length - 1] : 0;
  if (padByte < 1 || padByte > blockSize) return false;
  return plain.slice(plain.length - padByte).every(b => b === padByte);
}

Try / catch

import OperationError from "../errors/OperationError.mjs";
try {
  const pt = decryptWithBlockMode(ct, key, iv, mode, "PKCS5");
} catch (e) {
  if (e instanceof OperationError && /Invalid PKCS#5 padding/.test(e.message)) {
    // wrong key/IV or padding-scheme mismatch; do not return partial plaintext
  } else throw e;
}

Prevention

When it happens

Trigger: Decryption with padding='PKCS5' where the trailing byte is in range [1..8] but the preceding (padByte-1) bytes do not all equal padByte. Causes: wrong key producing plaintext whose final bytes look like a plausible but inconsistent pad; partial corruption of the last block; encryptor used a different padding scheme (ZERO/BIT) but decryptor expects PKCS5.

Common situations: Key/IV mismatch where the random tail happens to start with a byte in 1..8; encrypt/decrypt padding scheme mismatch; bit-flip in the last ciphertext block.

Related errors


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