agalwood/Motrix · error

Key must be 16 bytes, got ${key.length}

Error message

Key must be 16 bytes, got ${key.length}

What it means

Plain Error thrown by SegmentDecryptor.decrypt when the key argument is not exactly 16 bytes. This guards the public decrypt API (not the fetcher) against callers passing a wrong-size key buffer — a programming error rather than a network one, since decrypt has no URI context to report.

Source

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

    if (fetchKey) {
      this.defaultFetchKey = fetchKey
    } else {
      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)

View on GitHub (pinned to 1a708ee577)

Solutions

  1. At the call site, assert key.length === 16 before decrypt to get a clearer stack trace.
  2. Confirm the key source: AES-128 keys are 16 bytes; if you have a 32-byte key you need AES-256, not this decryptor.
  3. If the key is hex/base64 encoded, decode it (Buffer.from(k, 'hex'|'base64')) before passing.
  4. Check for accidental string-vs-Buffer confusion — passing the string form of a key makes length = 32 (hex) or more.

Example fix

// before
decryptor.decrypt(seg, keyFromString, iv) // keyFromString is 32 chars
// after
const key = Buffer.from(keyFromString, 'hex') // 16 bytes
decryptor.decrypt(seg, new Uint8Array(key), iv)
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

function isAes128Key(key: Uint8Array): boolean {
  return key instanceof Uint8Array && key.length === 16
}

Try / catch

try {
  decryptor.decrypt(ciphertext, key, iv)
} catch (e) {
  if (/Key must be 16 bytes/.test(String(e))) {
    // re-derive the key (decode hex/base64, or fetch the right one)
  } else throw e
}

Prevention

When it happens

Trigger: Calling decryptor.decrypt(ciphertext, key, iv) where key.length !== 16. Typical when a 24/32-byte AES-192/256 key is passed, when a hex/base64 string was not decoded, or when a subarray view was sliced incorrectly.

Common situations: Caller stored the key as a hex string and forgot to decode; key derived with a different KDF salt/length; off-by-one in Buffer.subarray;混淆 of key and IV variables at the call site.

Related errors


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