M3U8 播得動卻下載不了?排查並修正 AES-128 加密誤判

回顧 M3U8Online 轉 MP4 與播放清單檢視工具中的加密誤判:為何主播放清單沒有金鑰標籤仍可能是加密串流,以及 AES-128、IV、金鑰輪替和跨來源請求如何影響下載。

開發 M3U8 轉 MP4 和 M3U8 播放清單檢視器 時,我們收到一個看似矛盾的回報:同一個網址在播放器中可以正常播放,檢視器顯示「未加密」,但下載 MP4 時卻提示內容已加密。問題其實有兩個:檢視器把入口播放清單的局部資訊當成整段影片的結論;轉換器當時則尚未支援 AES-128 解密。若只改錯誤訊息,兩個問題都還在。

以下範例使用虛構網址與檔名,不含使用者的媒體網址或金鑰。本文測試結果來自我們的實作與 2026 年 10 月 1 日測試:我們用受控樣本檢查 IV 處理、金鑰輪替、加密與未加密片段切換,以及加密初始化區段;也在 Chromium 中完成一個使用者提供串流的轉換。我們並未測試所有來源、裝置或加密方案。

關鍵在子播放清單

一開始的加密檢查很簡單:檔案裡沒有 EXT-X-KEY,就回報未加密。但主播放清單可能只有不同畫質變體的連結,根本沒有媒體片段或金鑰宣告。

入口播放清單可能長這樣:

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

打開 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

入口檔案沒有金鑰宣告,不代表子播放清單也沒有。我們改為將這種主播放清單標示為「尚未確認」,並提示使用者檢查子播放清單。你可以分別開啟視訊變體或獨立音訊播放清單。檢視器只會回報目前開啟檔案的資訊,不會在背景下載並掃描所有分支。

主播放清單與媒體播放清單的差異,可參考 RFC 8216 的通訊協定概述。判斷加密狀態之前,先確認自己查看的是哪一層播放清單。

播放器已經替你完成解密

播放器可以逐段取得媒體和金鑰,解密後再送入播放管線。使用者通常不會看到獨立的解密步驟。畫面能播放,不代表下載到的片段原本就是明文。

我們的轉換器走的是另一條處理流程:取得完整點播串流,再於瀏覽器中封裝成 MP4。早期版本會拒絕加密串流,避免把密文直接交給媒體引擎。播放器能處理某種串流,不代表下載工具也具備相同能力。

我們同時修正了兩個地方:檢視器保留「未知」狀態,避免做出錯誤判斷;轉換流程則在確認條件符合支援範圍後,先解密片段再封裝。若只是移除「不支援加密」的檢查,後續收到的仍是無效媒體資料,無法產生正確影片。

AES-128 計算不難,狀態管理才容易出錯

我們使用瀏覽器原生的 Web Crypto API 執行 AES-CBC,沒有自行實作密碼演算法。MDN 的 SubtleCrypto.decrypt 文件說明了 API 用法與安全內容環境的要求。實作時真正需要留意的是每個資源各自使用的金鑰與 IV。

一個金鑰宣告會套用到後續片段,直到另一個宣告改變狀態。若整份播放清單只保留最後找到的金鑰,遇到輪替時,前面的片段就可能被錯誤的金鑰解密。我們的剖析器會為每個片段記錄當時的加密資訊;遇到 METHOD=NONE 時,則清除後續片段的加密狀態。

IV 也必須按片段處理。播放清單有明確提供 IV 時就使用它;否則以媒體序列號推導 16 位元組的值。若 EXT-X-MEDIA-SEQUENCE:100,第一個片段使用序列號 100,下一個則使用 101。這不是檢視器表格的列號。位元組順序規則詳見 RFC 8216 第 5.2 節。

金鑰回應必須包含預期的二進位資料。我們要求長度恰好是 16 位元組,因此不會把登入頁、錯誤 JSON 或十六進位文字當成有效金鑰。加密初始化區段還有額外條件:AES-128 的 EXT-X-MAP 必須明確指定 IV。若缺少 IV,我們會回報錯誤,而不是自行猜測。

為何在其他地方能播放,這裡卻取得不到金鑰?

轉換器必須能讀取主播放清單、子播放清單、媒體片段、初始化區段與金鑰。任何一個請求遭跨來源政策阻擋,都可能讓轉換失敗。原始網站的播放器可能使用不同的來源網域或授權方式,因此在原站播放成功,不代表我們的頁面也能讀取同一批資源。

尤其要檢查金鑰請求。即使 HTTP 狀態碼是 200,內容仍可能是登入頁。請查看回應類型與長度,不要在公開問題回報中貼出金鑰內容或含憑證的網址。我們只會在當次轉換工作中暫存匯入的金鑰,不寫入永久儲存或日誌。同一轉換內若遇到相同金鑰網址,可以重用已匯入的金鑰;工作結束後不會留給下一次轉換使用。

下載失敗時,可以依序檢查:

  1. 在檢視器確認入口是主播放清單還是媒體播放清單,再查看選取的視訊與音訊分支。
  2. 檢查 METHOD、金鑰 URI 與 IV,確認串流使用的是工具支援的一般 AES-128 格式。
  3. 在瀏覽器網路面板查看金鑰請求是否成功、是否遭跨來源限制,以及回應是否為長度正確的二進位資料。
  4. 若金鑰可讀取但解密失敗,檢查 IV、媒體序列號、金鑰輪替位置,以及片段是否完整。
  5. 若解密成功但 MP4 處理失敗,再檢查編碼格式、遺失片段或串流參數變化。

解密會增加多少負擔?

AES 解密透過瀏覽器原生 API 執行,不需要重新編碼影片。整體轉換時間還包括下載媒體、寫入引擎的記憶體檔案系統,以及產生 MP4。耗時比例會依裝置與網路而異,我們沒有可套用到所有情況的固定數字。

更需要留意的是記憶體。下載資料、解密後片段、引擎資料與輸出檔案可能同時存在。我們目前將下載量限制在 256 MiB,但這不代表瀏覽器記憶體使用量最多就是 256 MiB。長影片仍可能對可用記憶體有限的裝置造成壓力,細節可參考瀏覽器轉換與部署限制的開發紀錄。

目前轉換器支援可讀取金鑰、且使用支援之 identity 格式的一般 AES-128。SAMPLE-AES、DRM 與其他金鑰格式仍未支援;工具也無法還原遺失的金鑰或繞過來源網站的授權要求。

如何確認影片真的正確解密?

我們先以獨立產生的加密樣本,比對解密後的位元組是否與原始資料一致。接著測試隱含與明確指定的 IV、金鑰輪替,以及 METHOD=NONE。瀏覽器測試涵蓋加密 TS、含加密初始化區段的 fMP4、錯誤金鑰、無效密文與取消操作。

一份使用者提供的完整樣本有 412 個片段,輸出約 210.78 MiB。瀏覽器讀取到的解析度是 1920×1080,片長約 823.59 秒,並可跳轉到接近片尾的位置。這證明該樣本完成了下載、解密與重新封裝,不代表任何 M3U8 都能通用處理。

檢視器現在會區分「尚無足夠資訊」與已確認的結果,轉換器則明確加入了解密步驟。若遇到「能播但不能下載」,可以沿著播放清單選擇、資源請求、解密與重新封裝逐一追查。若問題出在資源路徑或片段排列,另請參閱相對網址、位元組範圍與初始化區段的處理紀錄。