全部產品
Search
文件中心

ApsaraVideo VOD:HLS(M3U8)標準加密改寫

更新時間:Jul 13, 2024

開啟M3U8標準加密改寫功能後,可以改寫HLS(HTTP Live Streaming)協議的M3U8檔案(Media Playlist,媒體播放清單)。改寫成功後會在M3U8檔案內#EXT-X-KEY標籤後面增加加密參數(包括密碼編譯演算法、密鑰URI地址和鑒權參數),用戶端收到被改寫的M3U8檔案以後,將會使用帶鑒權參數的密鑰URI來發起請求,從CDN節點擷取到密鑰以後將會使用對應的密碼編譯演算法和密鑰來解密TS檔案。即通過配置M3U8標準加密改寫功能,可以實現對HLS資料訪問過程的加密保護。

您可以參考以下內容,詳細瞭解HLS(M3U8)標準加密改寫:

背景資訊

HLS(HTTP Live Streaming的縮寫)是一個由蘋果公司提出的基於HTTP的流媒體網路傳輸協議。HLS協議基於HTTP協議,用戶端按照順序使用HTTP協議下載儲存在伺服器上的檔案。HLS協議規定,視頻的封裝格式是TS(Transport Stream),除了TS視頻檔案本身,還定義了用來控制播放的M3U8檔案(文字檔)。HLS協議的工作原理是把整個視頻流分割成一個個小的TS格式視頻檔案來傳輸,在開始一個流媒體會話時,用戶端會先下載一個包含TS檔案URL地址的M3U8檔案(相當於一個播放清單),給用戶端用於下載TS檔案。

HLS基本欄位
  • #EXTM3U:M3U8檔案頭,必須放在第一行。
  • EXT-X-MEDIA-SEQUENCE :第一個TS分區的序號,一般情況下是0,但是在直播情境下,這個序號標識直播段的起始位置; #EXT-X-MEDIA-SEQUENCE:0
  • #EXT-X-TARGETDURATION:每個分區TS的最大的時間長度; #EXT-X-TARGETDURATION:10 ,表示每個分區的最大時間長度是10秒。
  • #EXT-X-ALLOW-CACHE:是否允許cache,#EXT-X-ALLOW-CACHE:YES#EXT-X-ALLOW-CACHE:NO,預設情況下是YES。
  • #EXT-X-ENDLIST:M3U8檔案結束符。
  • #EXTINF:extra info,分區TS的資訊,如時間長度,頻寬等;一般情況下是 #EXTINF:<duration>,[<title>] 後面可以跟其他的資訊,逗號之前是當前分區的TS時間長度。分區時間長度要小於 #EXT-X-TARGETDURATION 定義的值。
  • #EXT-X-VERSION:M3U8版本號碼。
  • #EXT-X-DISCONTINUITY:該標籤表明其前一個切片與下一個切片之間存在中斷。
  • #EXT-X-PLAYLIST-TYPE :表明流媒體類型。
  • #EXT-X-KEY:是否加密解析。例如:#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/video.key?token=xxx" 密碼編譯演算法是AES-128,密鑰通過請求 https://example.com/video.key?token=xxx 來擷取,密鑰請求回來以後儲存在本地,並用於解密後續下載的TS視頻檔案。

技術原理

  1. 用戶端向CDN節點發起對M3U8檔案的訪問請求,例如:http://example.com/media/index.m3u8?MtsHlsUriToken=xxx
  2. CDN節點對用戶端的訪問請求進行校正,校正通過。
  3. CDN節點從來源站點下載原始M3U8檔案,並緩衝原始M3U8檔案。
  4. CDN節點對原始M3U8檔案的#EXT-X-KEY標籤進行改寫,增加加密方式、密鑰URI和鑒權參數,例如:#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/video.key?MtsHlsUriToken=xxx"
  5. CDN節點將改寫後的M3U8檔案返回給用戶端。
  6. 用戶端解析改寫後的M3U8檔案,拿到密鑰URI地址https://example.com/video.key?MtsHlsUriToken=xxx,並發起訪問請求。
  7. CDN節點收到用戶端請求,鑒權通過之後,將key檔案返回給用戶端。
  8. 用戶端繼續解析改寫後的M3U8檔案,從CDN節點下載其中的TS視頻檔案。
  9. 用戶端使用key檔案內的密鑰和前面#EXT-X-KEY標籤內定義的密碼編譯演算法來解密TS視頻檔案。

適用情境

HLS協議採用M3U8檔案來告知用戶端視頻檔案播放清單,用戶端拿到M3U8檔案以後就可以直接播放視頻,為了避免來源站點的視頻檔案被非授權用戶端訪問,需要對HLS協議使用的TS視頻檔案做加密,對TS視頻檔案做了加密以後,還需要告知用戶端解密方法,這裡就可以通過配置M3U8標準加密改寫功能,通過#EXT-X-KEY標籤來告知用戶端密碼編譯演算法、密鑰URI和鑒權key。

使用方法

  1. 在ApsaraVideo for VOD控制台開啟HLS標準加密參數透傳
    詳細步驟請參見HLS標準加密參數透傳
  2. 用戶端攜帶MtsHlsUriToken參數向CDN節點發起對M3U8檔案的訪問請求。
    其中,MtsHlsUriToken需要您自行搭建令牌服務,頒發使用者令牌(即產生MtsHlsUriToken)。
    下述代碼所產生的Token即是MtsHlsUriToken。下述Java範例程式碼中,您需要按實際情況變更的參數如下:
    參數傳入值
    ENCRYPT_KE加密Key,為使用者自訂字串,長度為16、24或32位。
    INIT_VECTOR加密位移量,為使用者自訂字串,長度為16位,不能含有特殊字元。
    import org.apache.commons.codec.binary.Base64;
    import org.apache.commons.lang3.StringUtils;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Arrays;
    
    public class PlayToken {
        //非AES產生方式,無需以下參數
        private static String ENCRYPT_KEY = ""; //加密Key,為使用者自訂的字串,長度為16、24或32位
        private static String INIT_VECTOR = ""; //加密位移量,為使用者自訂字串,長度為16位,不能含有特殊字元
    
        public static void main(String[] args) throws Exception {
    
            String serviceId = "12";
            PlayToken playToken = new PlayToken();
            String aesToken = playToken.generateToken(serviceId);
            //System.out.println("aesToken " + aesToken);
            //System.out.println(playToken.validateToken(aesToken));   //驗證解密部分
    
        }
        /**
         * 根據傳遞的參數產生令牌
         * 說明:
         *  1、參數可以是業務方的使用者ID、播放終端類型等資訊
         *  2、調用令牌介面時產生令牌Token
         * @param args
         * @return
         */
        public String generateToken(String... args) throws Exception {
            if (null == args || args.length <= 0) {
                return null;
            }
            String base = StringUtils.join(Arrays.asList(args), "_");
            //設定30S後,該token到期,到期時間可以自行調整
            long expire = System.currentTimeMillis() + 30000L;
            base += "_" + expire;   //自訂字串,base的長度為16位字元(此例中,時間戳記佔13位,底線(_)佔1位,則還需傳入2位字元。實際配置時也可按需全部更改,最終保證base為16、24或32位字串即可。)
            //產生token
            String token = encrypt(base, ENCRYPT_KEY);  //arg1為要加密的自訂字串,arg2為加密Key
            //儲存token,用於解密時校正token的有效性,例如:到期時間、token的使用次數
            saveToken(token);
            return token;
        }
    
        /**
         * 驗證token的有效性
         * 說明:
         *  1、解密介面在返回播放密鑰前,需要先校正Token的合法性和有效性
         *  2、強烈建議同時校正Token的到期時間以及Token的有效使用次數
         * @param token
         * @return
         * @throws Exception
         */
        public boolean validateToken(String token) throws Exception {
            if (null == token || "".equals(token)) {
                return false;
            }
            String base = decrypt(token,ENCRYPT_KEY); //arg1為解密字串,arg2為解密Key
            //先校正token的有效時間
            Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1));
            System.out.println("時間校正:" + expireTime);
            if (System.currentTimeMillis() > expireTime) {
                return false;
            }
            //從DB擷取token資訊,判斷token的有效性,業務方可自行實現
            TokenInfo dbToken = getToken(token);
            //判斷是否已經使用過該token
            if (dbToken == null || dbToken.useCount > 0) {
                return false;
            }
            //擷取到業務屬性資訊,用於校正
            String businessInfo = base.substring(0, base.lastIndexOf("_"));
            String[] items = businessInfo.split("_");
            //校正商務資訊的合法性,業務方實現
            return validateInfo(items);
        }
        /**
         * 儲存Token到DB
         * 業務方自行實現
         *
         * @param token
         */
        public void saveToken(String token) {
            //TODO 儲存Token
        }
        /**
         * 查詢Token
         * 業務方自行實現
         *
         * @param token
         */
        public TokenInfo getToken(String token) {
            //TODO 從DB擷取Token資訊,用於校正有效性和合法性
            return null;
        }
        /**
         * 校正商務資訊的有效性,業務方可自行實現
         *
         * @param infos
         * @return
         */
        public boolean validateInfo(String... infos) {
            //TODO 校正資訊的有效性,例如UID是否有效等
            return true;
        }
        /**
         * AES加密產生Token
         *
         * @param encryptStr  要加密的字串
         * @param encryptKey  加密Key
         * @return
         * @throws Exception
         */
        public String encrypt(String encryptStr, String encryptKey) throws Exception {
            IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(encryptKey.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, e);
            byte[] encrypted = cipher.doFinal(encryptStr.getBytes());
            return Base64.encodeBase64String(encrypted);
        }
        /**
         * AES解密token
         *
         * @param encryptStr  解密字串
         * @param decryptKey  解密Key
         * @return
         * @throws Exception
         */
        public String decrypt(String encryptStr, String decryptKey) throws Exception {
    
            IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(decryptKey.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, e);
    
            byte[] encryptByte = Base64.decodeBase64(encryptStr);
            byte[] decryptByte = cipher.doFinal(encryptByte);
            return new String(decryptByte);
        }
        /**
         * Token資訊,業務方可提供更多資訊,這裡僅給出樣本供參考
         */
        class TokenInfo {
            //Token的有效使用次數,分布式環境需要注意同步修改問題
            int useCount;
            //token內容
            String token;
        }}
                            
  3. CDN節點收到用戶端請求後,鑒權通過則解密播放檔案。
    若上述步驟二中產生的MtsHlsUriToken參數值為test,則當CDN解密播放時,會將MtsHlsUriToken=test追加到M3U8檔案中#EXT-X-KEY標籤的URI末尾。

    具體的鑒權校正邏輯您需要自行實現,可以參考播放HLS標準加密視頻中開啟M3U8標準加密改寫方式的解密服務的範例程式碼。