agalwood/Motrix · error

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

Error message

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

What it means

Plain Error thrown inside SegmentDecryptor's default key fetcher when the bytes downloaded from the key URI are not exactly 16 bytes long. AES-128 keys are definitionally 128 bits = 16 bytes; any other length means the URI did not return a key (HTML error page, wrong content, truncated response). The message includes both the actual length and the offending URI.

Source

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

import { createDecipheriv } from 'node:crypto'

export class SegmentDecryptor {
  private keyCache: Map<string, Promise<Uint8Array>>

  private defaultFetchKey: (uri: string) => Promise<Uint8Array>

  constructor(fetchKey?: (uri: string) => Promise<Uint8Array>) {
    this.keyCache = new Map()

    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)

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Fetch the key URI directly with curl and inspect the byte length and content type.
  2. Confirm the EXT-X-KEY URI in the manifest resolves to a raw 16-byte octet stream.
  3. If the key endpoint returns base64/hex, supply a custom fetchKey to SegmentDecryptor that decodes before returning.
  4. Check response.status before reading the body — a 404 returning HTML is the usual culprit.

Example fix

// before
const decryptor = new SegmentDecryptor()
// after — custom fetcher that validates status and decodes base64 keys
const decryptor = new SegmentDecryptor(async (uri) => {
  const res = await fetch(uri)
  if (!res.ok) throw new Error(`key fetch failed: HTTP ${res.status}`)
  const raw = await res.text()
  const buf = Buffer.from(raw.trim(), 'base64')
  if (buf.length !== 16) throw new Error(`bad key length ${buf.length}`)
  return new Uint8Array(buf)
})
Defensive patterns

Strategy: try-catch

Validate before calling

async function fetchAes128Key(uri: string): Promise<Uint8Array> {
  const res = await fetch(uri)
  if (!res.ok) throw new Error(`key HTTP ${res.status}`)
  const buf = new Uint8Array(await res.arrayBuffer())
  if (buf.length !== 16) throw new Error(`key at ${uri} is ${buf.length} bytes, not 16`)
  return buf
}
const decryptor = new SegmentDecryptor(fetchAes128Key)

Try / catch

try {
  const key = await decryptor.getKey(keyUri)
} catch (e) {
  if (/Key must be 16 bytes.*from/.test(String(e))) {
    // re-fetch the key URI manually to inspect; likely a 404 HTML page
  } else throw e
}

Prevention

When it happens

Trigger: getKey(uri) or defaultFetchKey(uri) where the fetched resource is not a raw 16-byte AES key — e.g. the URI returns a 404 HTML page (200–5000 bytes), a JSON error object, a binary key of the wrong size (24/32 bytes for AES-192/256), or an empty response.

Common situations: Expired key URL returning an error page; CORS/proxy injecting HTML; key endpoint migrated to return base64-encoded keys (which are ~24 bytes); wrong key URI resolved from a malformed EXT-X-KEY line; TLS/cert page interception.

Related errors


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