M3U8 speelt wel af maar downloadt niet: een fout in AES-128-detectie oplossen

Een terugblik op een fout in de encryptiedetectie van de M3U8Online MP4-converter en playlist-inspector: waarom een masterplaylist zonder sleutel-tag toch versleutelde media kan verbergen, en hoe AES-128, IV's, sleutelrotatie en cross-origin-verzoeken downloads beïnvloeden.

Bij de ontwikkeling van de M3U8-naar-MP4-converter en playlist-inspector kregen we een verwarrende melding. Een URL speelde normaal af. De inspector meldde dat de stream niet versleuteld was. Toch weigerde de converter te downloaden omdat de inhoud versleuteld zou zijn.

Beide tools moesten worden aangepast. De inspector trok op basis van alleen de instapplaylist een conclusie over de volledige stream, terwijl de converter nog geen AES-128-ontsleuteling ondersteunde. Alleen de foutmelding aanpassen zou geen van beide problemen hebben opgelost.

De voorbeelden hieronder gebruiken fictieve adressen en bestandsnamen. Ze bevatten geen URL's of sleutels van gebruikers. De testresultaten komen uit onze implementatie en tests van 1 oktober 2026: gecontroleerde fixtures voor IV-verwerking, sleutelrotatie, overgangen tussen versleutelde en onversleutelde segmenten en versleutelde initialisatiesecties, plus een volledige door een gebruiker aangeleverde stream die in Chromium is geconverteerd. We hebben niet elke bron, elk apparaat of elke encryptiemethode getest.

De sleutel stond in de onderliggende playlist

Onze eerste encryptiecontrole was te eenvoudig: als een bestand geen EXT-X-KEY bevatte, meldden we dat het niet versleuteld was. Een masterplaylist kan echter alleen links naar kwaliteitsvarianten bevatten, zonder mediafragmenten of sleutelverklaringen.

Een instapplaylist kan er zo uitzien:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=2091000,RESOLUTION=1920x1080
1080p/index.m3u8

De encryptieverklaring staat pas in 1080p/index.m3u8:

#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:100
#EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x00000000000000000000000000000000
#EXTINF:4.0,
segment-100.ts
#EXT-X-ENDLIST

Dat de masterplaylist geen sleutelverklaring bevat, zegt niets over de onderliggende playlist. We hebben de inspector daarom aangepast: bij zo'n masterplaylist blijft de encryptiestatus onbevestigd en krijgt de gebruiker het advies een onderliggende playlist te inspecteren. Je kunt video-varianten en aparte audio-playlists afzonderlijk openen. De inspector rapporteert over het bestand dat je hebt geopend; hij downloadt niet ongemerkt alle takken om ze te controleren.

Het onderscheid tussen master- en mediaplaylists staat beschreven in het protocoloverzicht van RFC 8216. Controleer eerst welk type playlist je voor je hebt voordat je een encryptieresultaat vertrouwt.

Een werkende speler heeft de ontsleuteling al uitgevoerd

Een speler kan elk segment ophalen, de bijbehorende sleutel verkrijgen, de gegevens ontsleutelen en het resultaat aan de afspeelpijplijn doorgeven. De meeste gebruikers zien geen aparte stap voor ontsleuteling. Dat er beeld verschijnt, bewijst niet dat het opgehaalde segment oorspronkelijk onversleuteld was.

Onze converter volgt een ander pad: hij haalt een volledige on-demandstream op en remuxt die in de browser naar MP4. De eerste versie weigerde versleutelde streams om te voorkomen dat ciphertext rechtstreeks in de media-engine terechtkwam. Ondersteuning in de speler betekent niet dat de downloadtool dezelfde mogelijkheid heeft.

We hebben twee zaken aangepast: de inspector laat een onbekende status staan in plaats van een onjuiste conclusie te tonen, en de converter ontsleutelt geschikte AES-128-segmenten voordat hij ze remuxt. Alleen de controle voor versleutelde streams verwijderen zou ongeldige mediabytes aan de volgende stap doorgeven.

De AES-128-berekening is eenvoudig; statusbeheer is lastiger

We gebruiken de native Web Crypto API van de browser voor AES-CBC en hebben geen eigen cipher geschreven. De documentatie van MDN voor SubtleCrypto.decrypt beschrijft de API en de eis van een beveiligde context. De lastige implementatie zit in het toewijzen van de juiste sleutel en IV aan elke resource.

Een sleutelverklaring blijft van toepassing op de daaropvolgende segmenten totdat een nieuwe verklaring die status wijzigt. Alleen de laatst gevonden sleutel voor de hele playlist bewaren zou segmenten van vóór een sleutelrotatie met de verkeerde sleutel ontsleutelen. Onze parser slaat de actieve encryptiegegevens per segment op. METHOD=NONE heft die status op voor de volgende segmenten.

Ook de IV verschilt per segment. Als de playlist een expliciete IV bevat, gebruiken we die. Anders leiden we de 16-bytewaarde af van het mediavolgnummer. Bij EXT-X-MEDIA-SEQUENCE:100 gebruikt het eerste segment nummer 100 en het volgende 101. Dat is niet hetzelfde als het rijnummer in de tabel van de inspector. De bytevolgorde staat in RFC 8216, sectie 5.2.

De sleutelrespons moet de verwachte binaire gegevens bevatten. We eisen precies 16 bytes, zodat een inlogpagina, foutmelding in JSON of hexadecimale tekst niet als sleutel wordt geaccepteerd. Versleutelde initialisatiesecties hebben een extra vereiste: een AES-128-EXT-X-MAP moet een expliciete IV bevatten. Ontbreekt die, dan melden we dat in plaats van er zelf een te verzinnen.

Waarom kan een video elders afspelen als de sleutel hier niet bereikbaar is?

Voor een conversie moet de browser de masterplaylist, onderliggende playlists, mediasegmenten, initialisatiesecties en sleutels kunnen lezen. Een cross-origin-beperking op één van die verzoeken kan de conversie al laten mislukken. De speler op de oorspronkelijke website kan vanaf een andere origin werken of een andere autorisatie gebruiken.

Let vooral op het sleutelverzoek. Een HTTP-status 200 kan nog steeds een inlogpagina bevatten. Controleer het responstype en de lengte. Deel nooit sleutelmateriaal of URL's met inloggegevens in openbare meldingen. Onze converter bewaart geïmporteerde sleutels alleen binnen de huidige conversie en schrijft ze niet naar permanente opslag of logs. Tijdens die taak kan dezelfde sleutel-URI opnieuw worden gebruikt; daarna blijft de sleutel niet bewaard voor een volgende conversie.

Controleer een mislukte download in deze volgorde:

  1. Open de URL van de masterplaylist in de inspector. Controleer bij een masterplaylist vervolgens de gekozen video- en audio-playlists.
  2. Controleer METHOD, de sleutel-URI en de IV. Kijk of de stream de gewone, door de tool ondersteunde AES-128-vorm gebruikt.
  3. Controleer in het netwerktabblad van de browser of het sleutelverzoek lukt, niet door CORS wordt geblokkeerd en binaire gegevens van de verwachte lengte teruggeeft.
  4. Is de sleutel leesbaar maar mislukt de ontsleuteling, controleer dan de IV, het mediavolgnummer, de momenten van sleutelrotatie en of het segment volledig is.
  5. Lukt de ontsleuteling maar faalt de MP4-verwerking, controleer dan codecs, ontbrekende segmenten en wijzigingen in streamparameters.

Hoeveel extra werk kost het ontsleutelen?

AES-ontsleuteling loopt via een native browser-API en vereist geen hercodering van de video. De totale verwerkingstijd bestaat ook uit het downloaden van media, het schrijven naar het in-memory-bestandssysteem van de engine en het maken van de MP4. De verhouding hangt af van apparaat en netwerk; we hebben geen vast percentage dat overal geldt.

Geheugengebruik vraagt meer aandacht. Gedownloade bytes, ontsleutelde segmenten, enginegegevens en het uitvoerbestand kunnen tegelijk in het geheugen staan. Onze downloadlimiet is 256 MiB, maar dat is geen belofte dat de browser maximaal 256 MiB RAM gebruikt. Lange video's kunnen nog steeds problemen geven op apparaten met weinig vrij geheugen. Zie ook onze ontwikkelnotities over browserconversie en deployment.

De converter ondersteunt momenteel gewone AES-128 met een bereikbare sleutel in de ondersteunde identity-indeling. SAMPLE-AES, DRM en andere sleutelindelingen worden niet ondersteund. De tool kan geen ontbrekende sleutel reconstrueren of toegangsbeperkingen van de bron omzeilen.

De uitvoervideo controleren

We vergeleken de ontsleuteling eerst met onafhankelijk gegenereerde versleutelde fixtures en controleerden of de ontsleutelde bytes overeenkwamen met de oorspronkelijke bytes. Daarna testten we impliciete en expliciete IV's, sleutelrotatie en METHOD=NONE. Browsercontroles omvatten versleutelde TS en fMP4 met een versleutelde initialisatiesectie, ongeldige sleutels, ongeldige ciphertext en annuleren.

De volledige, door een gebruiker aangeleverde sample bevatte 412 segmenten. Het MP4-bestand was ongeveer 210,78 MiB. De browser herkende een resolutie van 1920×1080 en een duur van circa 823,59 seconden; zoeken naar een positie dicht bij het einde werkte. Dat bevestigde downloaden, ontsleutelen en remuxen voor deze specifieke sample.

De inspector maakt nu onderscheid tussen ontbrekend bewijs en een bevestigde status, en de converter voert ontsleuteling uit als afzonderlijke stap. Als je stream wel afspeelt maar niet downloadt, traceer dan de fout via playlistkeuze, resourceverzoeken, ontsleuteling en remuxen. Problemen met resourcepaden of de indeling van segmenten bespreken we in onze notities over relatieve URL's, byte-ranges en initialisatiesecties.