HLS標準加密使用AES_128進行內容加密,適配所有HLS播放器,需要配合密鑰管理服務和令牌服務使用,被廣泛應用于在線教育、獨播劇等對內容安全性要求較高的領域。本文為您介紹如何對視頻進行HLS標準加密以及如何進行解密播放。
升級說明
視頻點播于2022年08月升級了HLS標準加密服務,優化了加密流程和邏輯,但舊的加密流程仍然可用,建議您使用新的加密流程。若因業務需要,您仍需使用舊的加密流程,可參考HLS標準加密(舊)。
工作原理
視頻點播采用信封數據加密的方式加密視頻。業務方調用阿里云密鑰管理服務(KMS)生成數據密鑰(DK)和信封數據密鑰(EDK),然后利用數據密鑰(DK)加密視頻,并將加密后的文件和信封數據密鑰(EDK)存儲。播放器終端通過解密服務獲取數據密鑰(DK)請求解密播放視頻。
如果您需要對解密地址進行安全驗證,可以通過開啟M3U8標準加密改寫(默認改寫的參數名為MtsHlsUriToken
)實現對HLS數據訪問過程的再次加密保護。M3U8標準加密改寫的開啟方式及技術原理請參見HLS(M3U8)標準加密改寫。
加密及解密流程
上傳及加密流程
解密播放流程
開啟M3U8標準加密改寫(推薦)
未開啟M3U8標準加密改寫
相關概念
概念 | 說明 |
訪問控制RAM(Resource Access Management) | 是阿里云提供的管理用戶身份與資源訪問權限的服務。更多信息,請參見訪問控制。 |
密鑰管理服務KMS(Key Management Service) | 是一站式密鑰管理和數據加密服務平臺、一站式憑據安全管理平臺,提供簡單、可靠、安全、合規的數據加密保護和憑據管理能力。更多信息,請參見密鑰管理服務。 |
數據密鑰DK(Data Key) | 數據密鑰是加密數據使用的明文數據密鑰。更多信息,請參見基本概念。 |
信封數據密鑰EDK(Enveloped Data Key/Encrypted Data Key) | 信封數據密鑰是通過信封加密技術保密后的密文數據密鑰。更多信息,請參見基本概念。 |
前提條件
加密視頻
創建Service Key。
Service Key是密鑰管理服務的一種加密主Key,接入標準加密的密鑰必須要使用該Service Key生成,若不創建Service Key,則后續調用GenerateKMSDataKey - 創建KMS數據密鑰接口生成密鑰時將會報錯。
登錄視頻點播控制臺,選擇配置管理 > 媒體處理配置 > 標準加密。
單擊頁面左上方的服務地域標識,切換到需要創建Service Key的服務地域下。
說明創建的Service Key與視頻存儲的源站地域必須一致,例如:視頻存儲在華東2(上海),則Service Key必須在華東2(上海)創建。
在標準加密頁面,單擊創建Service Key。
創建成功后,會提示您密鑰(Service Key)創建成功,且在密鑰信息區域可以查看您的Service Key。
說明若已提示密鑰創建成功,仍未看到Service Key,可能是由于服務角色缺失導致,您可以通過重新授權恢復角色后再刷新頁面查看Service Key。
創建用于HLS標準加密的轉碼模板組。
HLS標準加密過程中需要用到兩個轉碼模板組:不轉碼模板組和用于HLS標準加密的模板組。
其中,不轉碼模板組為系統內置的模板組,您無需創建可以直接使用;用于HLS標準加密的模板組需要您自行創建。創建方法如下:
搭建加密服務。
調用GenerateKMSDataKey - 創建KMS數據密鑰接口生成數據密鑰(DK)和信封數據密鑰(EDK)。
調用該接口無需傳任何參數,接口調用成功后,返回參數
CiphertextBlob
即為信封數據密鑰(EDK),返回參數Plaintext
即為數據密鑰(DK);其中,CiphertextBlob
是一個AES_128的密鑰,請保存該參數值用于后續發起標準加密轉碼時傳入。使用不轉碼模板組上傳視頻。
因在視頻點播中上傳視頻時會觸發自動轉碼,而自動觸發的轉碼暫不支持HLS標準加密,因此需要HLS標準加密的視頻,在上傳時需要使用系統內置的不轉碼模板組進行上傳,以防止上傳時自動觸發轉碼。
通過視頻點播控制臺上傳,請參見通過視頻點播控制臺上傳文件;通過服務端接口上傳,請參見媒體上傳。
配置視頻點播的事件通知,當接收到視頻上傳完成的回調消息時,則表明視頻已經上傳到視頻點播中。事件通知的配置方法,請參見事件通知。
發起HLS標準加密轉碼。
調用SubmitTranscodeJobs - 提交媒體轉碼作業接口,發起HLS標準加密轉碼。
下述Java示例代碼中,您需要按實際情況變更的參數如下:
參數
傳入值
request.setTemplateGroupId("")
傳入步驟二中創建的用于HLS標準加密的轉碼模板組ID。
request.setVideoId("")
傳入步驟四中上傳的待加密的視頻ID。
encryptConfig.put("CipherText","")
傳入步驟三中返回的
CiphertextBlob
值。encryptConfig.put("DecryptKeyUri","")
傳入密鑰URI地址,密鑰URI地址由解密服務地址及
CiphertextBlob
值拼接而成。以在本地的8099端口為例,密鑰URI地址如下:http://172.16.0.1:8099?CipherText=CiphertextBlob值
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.vod.model.v20170321.SubmitTranscodeJobsRequest; import com.aliyuncs.vod.model.v20170321.SubmitTranscodeJobsResponse; public class SubmitTranscodeJobs { // 阿里云賬號AccessKey擁有所有API的訪問權限,建議您使用RAM用戶進行API訪問或日常運維。 // 強烈建議不要把AccessKey ID和AccessKey Secret保存到工程代碼里,否則可能導致AccessKey泄露,威脅您賬號下所有資源的安全。 // 本示例通過從環境變量中讀取AccessKey,來實現API訪問的身份驗證。運行代碼示例前,請配置環境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。 private static String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); private static String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); public static SubmitTranscodeJobsResponse submitTranscodeJobs(DefaultAcsClient client) throws Exception{ SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest(); request.setTemplateGroupId(""); request.setVideoId(""); JSONObject encryptConfig = new JSONObject(); encryptConfig.put("CipherText",""); encryptConfig.put("DecryptKeyUri",""); encryptConfig.put("KeyServiceType","KMS"); request.setEncryptConfig(encryptConfig.toJSONString()); return client.getAcsResponse(request); } public static void main(String[] args) throws ClientException { String regionId = "cn-shanghai"; // 點播服務接入區域 DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); DefaultAcsClient client = new DefaultAcsClient(profile); SubmitTranscodeJobsResponse response; try { response = submitTranscodeJobs(client); System.out.println("RequestId is:"+response.getRequestId()); System.out.println("TranscodeTaskId is:"+response.getTranscodeTaskId()); System.out.println("TranscodeJobs is:"+ JSON.toJSON(response.getTranscodeJobs())); } catch (Exception e) { e.printStackTrace(); } } }
查看HLS標準加密結果。
轉碼完成后,您可以通過如下兩種方式來判斷標準加密是否成功。
方式一:登錄視頻點播控制臺,選擇媒資庫 > 音/視頻 > 管理 > 視頻地址,在視頻地址頁面,如果視頻有多種格式的輸出(例如還存在格式為mp4的原始文件),只需要查看m3u8格式的視頻流是否帶有標準加密字樣,如果存在,則表明標準加密成功。
方式二:將帶有標準加密字樣的M3U8文件的地址拷貝出來,使用命令
curl -v "M3U8文件地址"
,查看獲取到的M3U8內容是否存在URI="<業務方在發起標準加密時傳遞的解密URI地址,即DecryptKeyUri參數值>"
關鍵信息,有則表明為標準加密且加密成功。
播放HLS標準加密視頻
搭建解密服務。
搭建一個本地HTTP服務,用于解密視頻。
調用DecryptKMSDataKey - 解密KMS數據密鑰密文接口進行解密,該接口返回的數據密鑰(DK)即
PlainText
是GenerateKMSDataKey - 創建KMS數據密鑰接口返回的PlainText
經過base64_decode之后的數據。如果您需要對解密地址進行安全驗證,可以通過開啟M3U8標準加密改寫(默認改寫的參數名為
MtsHlsUriToken
)實現對HLS數據訪問過程的再次加密保護。M3U8標準加密改寫的開啟方式及技術原理請參見HLS(M3U8)標準加密改寫。根據您是否開啟M3U8標準加密改寫,搭建解密服務的代碼實現不同,具體如下:開啟M3U8標準加密改寫(推薦)
下述Java示例代碼僅供參考,您可以按實際需要自行實現解密及MtsHlsUriToken的校驗邏輯。下述示例代碼中,您需要按實際情況變更的參數如下:
參數
傳入值
region
地域標識,要求KMS服務的地域和點播視頻所在的地域保持一致。例如華東2(上海),填寫
cn-shanghai
,其他地域標識請參見點播地域標識。AccessKey
傳入阿里云賬號或RAM用戶的AccessKey ID和AccessKey Secret,獲取方法請參見創建AccessKey。
httpserver
根據需求選擇服務啟動的端口號。
import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.ProtocolType; import com.aliyuncs.vod.model.v20170321.DecryptKMSDataKeyRequest; import com.aliyuncs.vod.model.v20170321.DecryptKMSDataKeyResponse; import com.aliyuncs.profile.DefaultProfile; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.spi.HttpServerProvider; import org.apache.commons.codec.binary.Base64; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.URI;import java.util.regex.Matcher; import java.util.regex.Pattern; public class HlsDecryptServer { private static DefaultAcsClient client; static { //KMS的區域,必須與視頻對應區域 String region = ""; // 訪問KMS的授權AccessKey信息 // 阿里云賬號AccessKey擁有所有API的訪問權限,建議您使用RAM用戶進行API訪問或日常運維。 // 強烈建議不要把AccessKey ID和AccessKey Secret保存到工程代碼里,否則可能導致AccessKey泄露,威脅您賬號下所有資源的安全。 // 本示例通過從環境變量中讀取AccessKey,來實現API訪問的身份驗證。運行代碼示例前,請配置環境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。 String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); client = new DefaultAcsClient(DefaultProfile.getProfile(region, accessKeyId, accessKeySecret)); } /** * 說明: * 1、接收解密請求,獲取密文密鑰和用戶令牌Token * 2、調用KMS decrypt接口獲取明文密鑰 * 3、將明文密鑰Base64 decode返回 */ public class HlsDecryptHandler implements HttpHandler { /** * 處理解密請求 * @param httpExchange * @throws IOException */ public void handle(HttpExchange httpExchange) throws IOException { String requestMethod = httpExchange.getRequestMethod(); if ("GET".equalsIgnoreCase(requestMethod)) { //校驗token的有效性 String token = getMtsHlsUriToken(httpExchange); boolean validRe = validateToken(token); if (!validRe) { return; } //從URL中取得密文密鑰 String ciphertext = getCiphertext(httpExchange); if (null == ciphertext) return; //從KMS中解密出來,并Base64 decode byte[] key = decrypt(ciphertext); //設置header setHeader(httpExchange, key); //返回Base64 decode之后的密鑰 OutputStream responseBody = httpExchange.getResponseBody(); responseBody.write(key); responseBody.close(); } } private void setHeader(HttpExchange httpExchange, byte[] key) throws IOException { Headers responseHeaders = httpExchange.getResponseHeaders(); responseHeaders.set("Access-Control-Allow-Origin", "*"); httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, key.length); } /** * 調用KMS decrypt接口解密,并將明文Base64 decode * @param ciphertext * @return */ private byte[] decrypt(String ciphertext) { DecryptKMSDataKeyRequest request = new DecryptKMSDataKeyRequest(); request.setCipherText(ciphertext); request.setProtocol(ProtocolType.HTTPS); try { DecryptKMSDataKeyResponse response = client.getAcsResponse(request); String plaintext = response.getPlaintext(); System.out.println("PlainText: " + plaintext); //注意:需要Base64 decode return Base64.decodeBase64(plaintext); } catch (ClientException e) { e.printStackTrace(); return null; } } /** * 校驗令牌有效性 * @param token * @return */ private boolean validateToken(String token) { if (null == token || "".equals(token)) { return false; } //TODO 業務方實現令牌有效性校驗 return true; } /** * 從URL中獲取密文密鑰參數 * @param httpExchange * @return */ private String getCiphertext(HttpExchange httpExchange) { URI uri = httpExchange.getRequestURI(); String queryString = uri.getQuery(); String pattern = "CipherText=(\\w*)"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(queryString); if (m.find()) return m.group(1); else { System.out.println("Not Found CipherText Param"); return null; } } /** * 獲取Token參數 * * @param httpExchange * @return */ private String getMtsHlsUriToken(HttpExchange httpExchange) { URI uri = httpExchange.getRequestURI(); String queryString = uri.getQuery(); String pattern = "MtsHlsUriToken=(\\w*)"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(queryString); if (m.find()) return m.group(1); else { System.out.println("Not Found MtsHlsUriToken Param"); return null; } } } /** * 服務啟動 * * @throws IOException */ private void serviceBootStrap() throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); //監聽端口可以自定義,能同時接受最多30個請求 HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8099), 30); httpserver.createContext("/", new HlsDecryptHandler()); httpserver.start(); System.out.println("hls decrypt server started"); } public static void main(String[] args) throws IOException { HlsDecryptServer server = new HlsDecryptServer(); server.serviceBootStrap(); }}
未開啟M3U8標準加密改寫
下述Java示例代碼中,您需要按實際情況變更的參數如下:
參數
傳入值
region
地域標識,要求KMS服務的地域和點播視頻所在的地域保持一致。例如華東2(上海),填寫
cn-shanghai
,其他地域標識請參見點播地域標識。AccessKey
傳入阿里云賬號或RAM用戶的AccessKey ID和AccessKey Secret,獲取方法請參見創建AccessKey。
httpserver
根據需求選擇服務啟動的端口號。
import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.ProtocolType; import com.aliyuncs.profile.DefaultProfile; import com.aliyuncs.vod.model.v20170321.DecryptKMSDataKeyRequest; import com.aliyuncs.vod.model.v20170321.DecryptKMSDataKeyResponse; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.spi.HttpServerProvider; import org.apache.commons.codec.binary.Base64; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.URI; import java.util.regex.Matcher; import java.util.regex.Pattern; public class HlsDecryptServerNoToken { private static DefaultAcsClient client; static { //KMS的區域,必須與視頻對應區域 String region = "cn-beijing"; //訪問KMS的授權AccessKey信息 // 阿里云賬號AccessKey擁有所有API的訪問權限,建議您使用RAM用戶進行API訪問或日常運維。 // 強烈建議不要把AccessKey ID和AccessKey Secret保存到工程代碼里,否則可能導致AccessKey泄露,威脅您賬號下所有資源的安全。 // 本示例通過從環境變量中讀取AccessKey,來實現API訪問的身份驗證。運行代碼示例前,請配置環境變量ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET。 String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); client = new DefaultAcsClient(DefaultProfile.getProfile(region, accessKeyId, accessKeySecret)); } /** * 說明: * 1、接收解密請求,獲取密文密鑰和令牌Token * 2、調用KMS decrypt接口獲取明文密鑰 * 3、將明文密鑰base64decode返回 */ public class HlsDecryptHandler implements HttpHandler { /** * 處理解密請求 * @param httpExchange * @throws IOException */ public void handle(HttpExchange httpExchange) throws IOException { String requestMethod = httpExchange.getRequestMethod(); if ("GET".equalsIgnoreCase(requestMethod)) { //從URL中取得密文密鑰 String ciphertext = getCiphertext(httpExchange); System.out.println(ciphertext); if (null == ciphertext) return; //從KMS中解密出來,并Base64 decode byte[] key = decrypt(ciphertext); //設置header setHeader(httpExchange, key); //返回base64decode之后的密鑰 OutputStream responseBody = httpExchange.getResponseBody(); responseBody.write(key); responseBody.close(); } } private void setHeader(HttpExchange httpExchange, byte[] key) throws IOException { Headers responseHeaders = httpExchange.getResponseHeaders(); responseHeaders.set("Access-Control-Allow-Origin", "*"); httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, key.length); } /** * 調用KMS decrypt接口解密,并將明文base64decode * @param ciphertext * @return */ private byte[] decrypt(String ciphertext) { DecryptKMSDataKeyRequest request = new DecryptKMSDataKeyRequest(); request.setCipherText(ciphertext); request.setProtocol(ProtocolType.HTTPS); try { DecryptKMSDataKeyResponse response = client.getAcsResponse(request); String plaintext = response.getPlaintext(); System.out.println("PlainText: " + plaintext); //注意:需要base64 decode return Base64.decodeBase64(plaintext); } catch (ClientException e) { e.printStackTrace(); return null; } } /** * 從URL中獲取密文密鑰參數 * @param httpExchange * @return */ private String getCiphertext(HttpExchange httpExchange) { URI uri = httpExchange.getRequestURI(); String queryString = uri.getQuery(); String pattern = "CipherText=(\\w*)"; Pattern r = Pattern.compile(pattern); Matcher m = r.matcher(queryString); if (m.find()) return m.group(1); else { System.out.println("Not Found CipherText Param"); return null; } } } /** * 服務啟動 * * @throws IOException */ private void serviceBootStrap() throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); //監聽端口可以自定義,能同時接受最多30個請求 HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8099), 30); httpserver.createContext("/", new HlsDecryptHandler()); httpserver.start(); System.out.println("hls decrypt server started"); } public static void main(String[] args) throws IOException { HlsDecryptServerNoToken server = new HlsDecryptServerNoToken(); server.serviceBootStrap(); }}
獲取視頻播放地址和憑證。
調用GetVideoPlayAuth - 獲取音視頻播放憑證接口獲取播放憑證或調用GetPlayInfo - 獲取音視頻播放地址接口獲取播放地址。
播放加密視頻。
HLS標準加密支持所有的HLS播放器,您可以選擇使用自研的播放器或阿里云播放器進行播放加密視頻。
如果使用阿里云播放器,請按照阿里云播放器的要求獲取令牌和鑒權信息后播放,詳情請參見播放HLS標準加密視頻-Web端。如果使用非阿里云播放器,請自行實現播放邏輯。
使用阿里云播放器測試播放的內部流程解析如下:
開啟M3U8標準加密改寫(推薦)
流程
獲取M3U8文件地址后,播放器會解析M3U8文件中的EXT-X-KEY標簽中的URI并訪問,從而獲取到帶密文密鑰的解密接口URI,此URI為您發起標準加密時傳遞的加密配置 EncryptConfig中的
DecryptKeyUri
參數值。只允許合法用戶才可以訪問,那么需要播放器在獲取解密密鑰時攜帶您承認的認證信息,認證信息可以通過MtsHlsUriToken參數傳入。
播放器在解析到解密地址URI時會自動請求解密接口獲取解密密鑰,拿到解密密鑰去解密加密過的ts文件進行播放。
示例
視頻的播放地址為:
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8
,則請求時需要攜帶MtsHlsUriToken
參數傳入最終請求地址為:
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8?MtsHlsUriToken=<令牌>
解密地址為:
https://demo.aliyundoc.com?CipherText=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****
最終解密請求地址為:
https://demo.aliyundoc.com?CipherText=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****&MtsHlsUriToken=<頒發的令牌>
未開啟M3U8標準加密改寫
流程
獲取M3U8文件地址后,播放器會解析M3U8文件中的
EXT-X-KEY
標簽中的URI并訪問,從而獲取到帶密文密鑰的解密接口URI,此URI為您發起標準加密時傳遞的加密配置 EncryptConfig中的DecryptKeyUri
參數值。播放器在解析到解密地址URI時會自動請求解密接口獲取解密密鑰,拿到解密密鑰去解密加密過的ts文件進行播放。
示例
視頻的播放地址為:
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8
最終請求地址為:
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8
解密地址為:
https://demo.aliyundoc.com?CipherText=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****
最終解密請求地址為:
https://demo.aliyundoc.com?CipherText=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****