{"record":{"id":"5fbb340ee76ae45c","repo":"agalwood/Motrix","slug":"key-must-be-16-bytes-got-key-length-from-uri","errorCode":null,"errorMessage":"Key must be 16 bytes, got ${key.length} from ${uri}","messagePattern":"Key must be 16 bytes, got (.+?) from (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/core/media/segment-decryptor.ts","lineNumber":19,"sourceCode":"import { createDecipheriv } from 'node:crypto'\n\nexport class SegmentDecryptor {\n  private keyCache: Map<string, Promise<Uint8Array>>\n\n  private defaultFetchKey: (uri: string) => Promise<Uint8Array>\n\n  constructor(fetchKey?: (uri: string) => Promise<Uint8Array>) {\n    this.keyCache = new Map()\n\n    if (fetchKey) {\n      this.defaultFetchKey = fetchKey\n    } else {\n      this.defaultFetchKey = async (uri: string) => {\n        const response = await fetch(uri)\n        const buffer = await response.arrayBuffer()\n        const key = new Uint8Array(buffer)\n        if (key.length !== 16) {\n          throw new Error(`Key must be 16 bytes, got ${key.length} from ${uri}`)\n        }\n        return key\n      }\n    }\n  }\n\n  decrypt(ciphertext: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array {\n    // Validate key and IV lengths\n    if (key.length !== 16) {\n      throw new Error(`Key must be 16 bytes, got ${key.length}`)\n    }\n    if (iv.length !== 16) {\n      throw new Error(`IV must be 16 bytes, got ${iv.length}`)\n    }\n\n    // Convert Uint8Array to Buffer for crypto operations\n    const keyBuffer = Buffer.from(key)\n    const ivBuffer = Buffer.from(iv)","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/media/segment-decryptor.ts#L1-L37","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fetch the key URI directly with curl and inspect the byte length and content type.","Confirm the EXT-X-KEY URI in the manifest resolves to a raw 16-byte octet stream.","If the key endpoint returns base64/hex, supply a custom fetchKey to SegmentDecryptor that decodes before returning.","Check response.status before reading the body — a 404 returning HTML is the usual culprit."],"exampleFix":"// before\nconst decryptor = new SegmentDecryptor()\n// after — custom fetcher that validates status and decodes base64 keys\nconst decryptor = new SegmentDecryptor(async (uri) => {\n  const res = await fetch(uri)\n  if (!res.ok) throw new Error(`key fetch failed: HTTP ${res.status}`)\n  const raw = await res.text()\n  const buf = Buffer.from(raw.trim(), 'base64')\n  if (buf.length !== 16) throw new Error(`bad key length ${buf.length}`)\n  return new Uint8Array(buf)\n})","handlingStrategy":"try-catch","validationCode":"async function fetchAes128Key(uri: string): Promise<Uint8Array> {\n  const res = await fetch(uri)\n  if (!res.ok) throw new Error(`key HTTP ${res.status}`)\n  const buf = new Uint8Array(await res.arrayBuffer())\n  if (buf.length !== 16) throw new Error(`key at ${uri} is ${buf.length} bytes, not 16`)\n  return buf\n}\nconst decryptor = new SegmentDecryptor(fetchAes128Key)","typeGuard":null,"tryCatchPattern":"try {\n  const key = await decryptor.getKey(keyUri)\n} catch (e) {\n  if (/Key must be 16 bytes.*from/.test(String(e))) {\n    // re-fetch the key URI manually to inspect; likely a 404 HTML page\n  } else throw e\n}","preventionTips":["Provide a custom fetchKey to SegmentDecryptor that checks res.ok and content type before reading the body.","Confirm EXT-X-KEY URIs resolve to raw 16-byte octet streams by curling them once during integration.","Handle base64/hex-encoded key endpoints by decoding in the custom fetcher."],"tags":["media","encryption","network","hls","aes"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}