agalwood/Motrix · error

IV must be 16 bytes, got ${iv.length}

Error message

IV must be 16 bytes, got ${iv.length}

What it means

Plain Error thrown by SegmentDecryptor.decrypt when the iv argument is not exactly 16 bytes. AES-128-CBC IVs are exactly one block (128 bits = 16 bytes); a different length means the caller built the IV wrong (e.g. used a 32-bit value, an 8-byte uint64, or a hex string).

Source

Thrown at src/core/media/segment-decryptor.ts:32

      this.defaultFetchKey = async (uri: string) => {
        const response = await fetch(uri)
        const buffer = await response.arrayBuffer()
        const key = new Uint8Array(buffer)
        if (key.length !== 16) {
          throw new Error(`Key must be 16 bytes, got ${key.length} from ${uri}`)
        }
        return key
      }
    }
  }

  decrypt(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array {
    // Validate key and IV lengths
    if (key.length !== 16) {
      throw new Error(`Key must be 16 bytes, got ${key.length}`)
    }
    if (iv.length !== 16) {
      throw new Error(`IV must be 16 bytes, got ${iv.length}`)
    }

    // Convert Uint8Array to Buffer for crypto operations
    const keyBuffer = Buffer.from(key)
    const ivBuffer = Buffer.from(iv)
    const ciphertextBuffer = Buffer.from(ciphertext)

    // Try with PKCS7 auto-padding first
    try {
      const decipher = createDecipheriv('aes-128-cbc', keyBuffer, ivBuffer)
      const plaintext = Buffer.concat([
        decipher.update(ciphertextBuffer),
        decipher.final(),
      ])
      return new Uint8Array(plaintext)
    } catch (err) {
      // Only fall back if error is PKCS7-padding related
      const msg = err instanceof Error ? err.message.toLowerCase() : ''

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Use the provided seqNumberIv(seq) helper to build sequence-number IVs — it always returns 16 bytes.
  2. If you have an explicit IV hex string, decode it: Buffer.from(ivHex.replace(/^0x/, '').padStart(32, '0'), 'hex').
  3. At the call site assert iv.length === 16 for a clearer error before decrypt.
  4. Do not reuse AES-GCM 12-byte nonces for AES-128-CBC — they are different cipher IV shapes.

Example fix

// before — passing an 8-byte bigint IV
decryptor.decrypt(seg, key, Buffer.from([...bigIntToBytes(seq, 8)]))
// after — use the helper that produces a 16-byte IV
import { seqNumberIv } from './segment-plan'
decryptor.decrypt(seg, key, seqNumberIv(seq))
Defensive patterns

Strategy: validation

Validate before calling

function isAes128Iv(iv: Uint8Array): boolean {
  return iv.length === 16
}
if (!isAes128Iv(iv)) {
  throw new Error(`iv must be 16 bytes; got ${iv.length}`)
}
decryptor.decrypt(ciphertext, key, iv)

Type guard

function isAes128Iv(iv: Uint8Array): boolean {
  return iv instanceof Uint8Array && iv.length === 16
}

Try / catch

try {
  decryptor.decrypt(ciphertext, key, iv)
} catch (e) {
  if (/IV must be 16 bytes/.test(String(e))) {
    // rebuild IV via seqNumberIv(seq) or decode hex
  } else throw e
}

Prevention

When it happens

Trigger: Calling decryptor.decrypt(ciphertext, key, iv) where iv.length !== 16. Typical when the caller passed a 4-byte sequence number, an 8-byte Buffer from BigInt, or a 32-char hex string instead of the decoded 16-byte form.

Common situations: Caller used seqNumberIv correctly elsewhere but built a custom IV wrong here; passed the IV as a hex string; confused the IV with a shorter nonce from another cipher (AES-GCM uses 12-byte nonces); off-by-one subarray slice.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/daa9fcba619ca1fb. Report an issue: GitHub.