embassy-rs/embassy · error

Input must be a multiple of

Error message

Input must be a multiple of {} bytes in ECB and CBC modes. Consider padding or ciphertext stealing.

What it means

For ECB and CBC the CRYP hardware applies no automatic padding, so the entire input — including the final block — must be an exact multiple of the AES block size. The driver panics with the block size in the {} slot, suggesting software padding or ciphertext stealing.

Solutions

  1. Apply PKCS#7 (or other) padding in software before the final ECB/CBC block; strip it after decrypt.
  2. Implement ciphertext stealing (CBC-CTS) for the tail blocks, as the message suggests.
  3. Switch to CTR or GCM, which handle arbitrary lengths natively.

Example fix

// before
let pt = *b"hello world"; // 11 bytes
ctx.payload(&pt, &mut out, true); // ECB/CBC panics
// after
let mut pt = *b"hello world\u{5}\u{5}\u{5}\u{5}\u{5}"; // PKCS#7 pad to 16
ctx.payload(&pt, &mut out, true);
Defensive patterns

Strategy: validation

Validate before calling

if input.len() % 16 != 0 { /* apply PKCS#7 padding or use CTR/GCM instead */ }

Prevention

When it happens

Trigger: Calling encrypt/decrypt in ECB or CBC mode with input length not divisible by 16 bytes (e.g. 30-byte plaintext with last_block=true).

Common situations: Encrypting raw records whose lengths aren't block-aligned; switching from a software AES-CBC that pads to this hardware driver; padding handled at an unexpected layer.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/1dd9453627e3b167. Report an issue: GitHub.

Appendix: source

Thrown at embassy-stm32/src/cryp/mod.rs:1428

                T::regs().cr().modify(|w| w.set_gcm_ccmph(2));
                T::regs().cr().modify(|w| w.set_fflush(true));
                T::regs().cr().modify(|w| w.set_crypen(true));
            }
        }
        if ctx.last_block_processed {
            panic!("The last block has already been processed!");
        }
        if input.len() > output.len() {
            panic!("Output buffer length must match input length.");
        }
        if !last_block {
            if last_block_remainder != 0 {
                panic!("Input length must be a multiple of {} bytes.", C::BLOCK_SIZE);
            }
        }
        if C::REQUIRES_PADDING {
            if last_block_remainder != 0 {
                panic!(
                    "Input must be a multiple of {} bytes in ECB and CBC modes. Consider padding or ciphertext stealing.",
                    C::BLOCK_SIZE
                );
            }
        }
        if last_block {
            ctx.last_block_processed = true;
        }

        // Load data into core, block by block.
        let num_full_blocks = input.len() / C::BLOCK_SIZE;
        for block in 0..num_full_blocks {
            let index = block * C::BLOCK_SIZE;
            // Write block in
            self.write_bytes_blocking(C::BLOCK_SIZE, &input[index..index + C::BLOCK_SIZE]);
            // Read block out
            self.read_bytes_blocking(C::BLOCK_SIZE, &mut output[index..index + C::BLOCK_SIZE]);
        }

View on GitHub (pinned to 463a07b963)