視頻加密旨在通過對視頻內容進行深度安全處理,確保視頻數據不被非法獲取和傳播,可有效防止視頻泄露和盜鏈問題,廣泛用于在線教育及財經等對內容安全性要求高的領域。阿里云目前支持兩種加密方式:阿里云私有加密(推薦)和HLS標準加密。本文介紹媒體處理HLS加密的原理和接入流程,幫助用戶更好的理解和實施HLS加密,在保證視頻安全的同時,實現流暢的在線播放體驗。
工作原理
相關概念
媒體處理采用信封數據加密的方式加密視頻。業務方調用阿里云密鑰管理服務(KMS)生成數據密鑰(DK)和信封數據密鑰(EDK),然后利用數據密鑰(DK)加密視頻,并將加密后的文件和信封數據密鑰(EDK)存儲。播放器終端通過解密服務獲取數據密鑰(DK)請求解密播放視頻。
HLS加密要求用戶自己保護數據密鑰(DK)。
概念 | 說明 |
數據密鑰DK(Data Key) | 也稱明文密鑰,生成后用于視頻加密。 |
信封數據密鑰EDK(Enveloped Data Key/Encrypted Data Key) | 也稱密文密鑰,是通過信封加密技術保密后的密文數據密鑰。主要用于解密數據密鑰,得到明文數據密鑰。 |
訪問控制RAM(Resource Access Management) | 是阿里云提供的管理用戶身份與資源訪問權限的服務。更多信息,請參見什么是訪問控制。 |
密鑰管理服務KMS(Key Management Service) | 是一站式密鑰管理和數據加密服務平臺、一站式憑據安全管理平臺,提供簡單、可靠、安全、合規的數據加密保護和憑據管理能力。更多信息,請參見什么是密鑰管理服務。 |
對象存儲OSS(Object Storage Service) | 阿里云提供的數據存儲服務,媒體處理操作的媒體資源均存放在OSS的Bucket中。更多信息,請參見什么是對象存儲OSS。 |
內容分發網絡CDN(Content Delivery Network) | 阿里云提供的內容分發網絡,在HLS加密流程中,CDN會動態修改M3U8文件中的解密URI并返回給播放端。更多信息,請參見什么是阿里云CDN。 |
加密流程
媒體處理的視頻加密流程如下:
業務方開通阿里云媒體處理服務(MPS)、存儲服務(OSS)、訪問控制(RAM)、密鑰管理服務(KMS)以及內容分發網絡(CDN)(如未開通)。
業務方授權媒體處理訪問剛開通的阿里云密鑰管理服務(KMS)。
說明授權訪問是為了媒體處理在加密視頻階段調用KMS的GenerateDataKey接口生成數據密鑰(DK)和信封數據密鑰(EDK)。
業務方配置OSS輸出Bucket域名為CDN加速域名,配置OSS域名CNAME及回源Host。
業務方創建視頻加密工作流,傳入OSS輸出Bucket、Key URI等關鍵信息。
Key URI是業務方的服務地址。媒體處理完成視頻加密后的M3U8文件中會包含Key URI信息。
業務方上傳需要加密的視頻,上傳過程中指定創建好的視頻加密工作流。
視頻上傳完成后媒體處理自動觸發加密轉碼。
媒體處理調用GenerateDataKey接口生成數據密鑰(DK)和信封數據密鑰(EDK),并使用數據密鑰(DK)加密視頻。加密完成后,將業務方提供的Key URI與信封數據密鑰(EDK)寫入M3U8文件。
媒體處理將M3U8文件及ts文件存入OSS的輸出Bucket中。
解密流程
播放端解密播放HLS加密視頻的流程如下:
業務方搭建令牌服務,用于頒發令牌MtsHlsUriToken。
重要令牌服務指用于派發MtsHlsUriToken的服務。
業務方調用KMS解密接口搭建解密服務,用于解密視頻,同時提供數據密鑰(DK)給播放終端。
重要KMS返回Base64加密后的數據密鑰給業務方。業務方需要將調用KMS接口獲得的數據密鑰Base64 Decode之后返回給播放終端。
業務方調用MPS的QueryMediaList接口獲取視頻M3U8文件的OSS地址,并將地址拼接MtsHlsUriToken后返回給播放終端。
播放終端攜帶MtsHlsUriToken、數據密鑰向阿里云CDN請求播放地址,阿里云CDN改寫M3U8文件,將業務方的Key URI與信封加密密鑰返回播放終端。播放終端解密播放視頻。
業務方實現概覽
在完整的HLS加密視頻和解密播放流程中,需要業務方自行實現的代碼邏輯包括:
創建加密工作流。
說明盡管控制臺也可以創建加密工作流,但為了更完整高效地使用HLS標準加密服務,建議您集成服務端SDK后創建。
搭建頒發及驗證MtsHlsUriToken令牌服務,并校驗解密令牌。推薦一個令牌只允許使用一次。
調用KMS服務的解密接口搭建解密服務,并將調用KMS接口獲得的明文密鑰Base64 Decode之后返回播放終端。
前提條件
接入媒體處理的HLS加密功能需要完成以下準備:
開通相關阿里云服務。
如果您尚未開通MPS,OSS,KMS, RAM,CDN服務,請先開通服務。
開通MPS服務。具體操作,請參見開通MPS服務。
開通OSS服務。具體操作,請參見開通OSS服務。
開通KMS服務。具體操作,請參見開通KMS服務。
開通RAM服務并授權。具體操作,請參見創建RAM用戶并授權。
開通CDN服務。具體操作,請參見開通CDN服務。
授權媒體處理訪問KMS。
登錄RAM訪問控制臺。
單擊授權進入新增授權頁面。
在授權主體搜索框搜索AliyunMtsDefaultRole,選擇系統創建的可供媒體處理使用的角色。
在選擇權限下方的搜索框搜索KMS并選擇AliyunKMSFullAccess,然后單擊確認新增授權。
完成授權后,媒體處理可以訪問您的KMS服務。收到加密視頻請求時,媒體處理調取KMS接口獲取數據密鑰加密視頻。
配置OSS輸出Bucket域名及回源Host。具體操作,請參見配置加速域名。如已配置請忽略。
說明OSS域名可以手動輸入阿里云OSS Bucket的外網域名(OSS外網域名可前往OSS控制臺查看),如:
exampleBucket****.oss-cn-hangzhou.aliyuncs.com
,也可以直接選擇同賬號下需要加速的OSS Bucket。不支持OSS內網域名。
加密視頻
請按以下指引完成視頻加密:
創建加密工作流。
創建加密工作流需要集成阿里云SDK并添加媒體處理相關依賴。請根據您使用的開發語言選擇查看對應代碼示例。
重要創建加密工作流時必須傳入業務方的Key URI地址,媒體處理加密視頻時將該地址寫入M3U8文件存儲到OSS。Key URI示例:
example.aliyundoc.com
。語言
集成SDK
加密工作流代碼示例
Java
Python
PHP
Node.js
上傳視頻觸發加密轉碼。您可以通過媒體處理控制臺或OSS控制臺上傳視頻。具體操作請參見上傳視頻。
說明由于加密工作流已經創建,上傳視頻時指定創建好的加密工作流即可自動觸發媒體處理加密轉碼。
加密完成后,可登錄OSS控制臺,在輸出Bucket文件路徑下查看M3U8文件。文件示例如下:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:5 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-KEY:METHOD=AES-128,URI="https://example.aliyundoc.com?Ciphertext=aabbccddeeff&MediaId=fbbf98691ea44b7c82dd75c5bc8b****" #EXTINF:4.127544, 15029611683170-00001.ts #EXT-X-ENDLIST
示例中,業務方配置的Key URI與媒體處理從KMS獲取的信封數據密鑰(EDK)包含在URI中。
播放HLS加密視頻
請按以下指引完成視頻解密播放:
搭建令牌服務。
說明令牌服務需要根據您的加密邏輯自行搭建,以達到更高的視頻安全等級。
搭建解密服務。
搭建一個本地HTTP服務,用于解密視頻和獲取解密密鑰。媒體處理提供Java及Python代碼示例。
Java代碼示例
Java SDK需要的依賴如下:
解密示例Base64:
import com.sun.net.httpserver.*; import com.sun.net.httpserver.spi.HttpServerProvider; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.InetSocketAddress; /** * ***** 使用須知 ****** * 本demo Base64方式解密服務代碼示例 * demo中端口號為8888, 請注意與KeyUri解密服務端口保持一致 * 如果您需要額外的Token令牌校驗, 請參考kms方式解密服務的實現邏輯 * * ***** 邏輯概述 ****** * 1、接收解密請求,獲取密文密鑰 * 2、將明文密鑰base64decode返回 */ public class Base64DecryptServer { public static void main(String[] args) throws IOException { Base64DecryptServer server = new Base64DecryptServer(); server.startService(); } public class Base64DecryptHandler implements HttpHandler { /** * 處理解密請求 * @param httpExchange * @throws IOException */ public void handle(HttpExchange httpExchange) throws IOException { String requestMethod = httpExchange.getRequestMethod(); if ("GET".equalsIgnoreCase(requestMethod)) { // 此處的解密密鑰需要和加密時候的密鑰一致 byte[] key = "encryptionkey128".getBytes(); // 設置header setHeader(httpExchange, key); // 返回base64decode之后的密鑰 OutputStream responseBody = httpExchange.getResponseBody(); System.out.println(new String(key)); 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); } } /** * 服務啟動 * @throws IOException */ private void startService() throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); //監聽端口9999,能同時接受30個請求 HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8888), 30); httpserver.createContext("/", new Base64DecryptHandler()); httpserver.start(); System.out.println("base64 hls decrypt server started"); } }
解密示例KMS:
import com.aliyun.mps.sdk.utils.InitClient; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.ProtocolType; import com.aliyuncs.kms.model.v20160120.DecryptRequest; import com.aliyuncs.kms.model.v20160120.DecryptResponse; import com.sun.net.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; /** * ***** 使用須知 ****** * 本demo 為未開啟標準加密改寫時的kms方式解密服務代碼示例, 不包含MtsHlsUriToken的校驗邏輯 * demo中端口號為8888, 請注意與KeyUri解密服務端口保持一致 * * ***** 邏輯概述 ****** * 1、接收解密請求,獲取密文密鑰 * 2、調用KMS decrypt接口通過Ciphertext獲取明文密鑰 * 3、將明文密鑰base64decode返回 */ public class HlsDecryptServerNoToken { private static DefaultAcsClient client; static { try{ client = InitClient.initMpsClient(); }catch (Exception e){ e.printStackTrace(); } } public static void main(String[] args) throws IOException { HlsDecryptServerNoToken server = new HlsDecryptServerNoToken(); server.startService(); } public class HlsDecryptHandler implements HttpHandler { public void handle(HttpExchange httpExchange) throws IOException { String requestMethod = httpExchange.getRequestMethod(); if(requestMethod.equalsIgnoreCase("GET")){ //從URL中取得密文密鑰 String ciphertext = getCiphertext(httpExchange); System.out.println(ciphertext); if (null == ciphertext) return; //從KMS中解密出來,并Base64 decode byte[] key = decrypt(ciphertext); //設置header setHeader(httpExchange, key); //返回密鑰 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); } private byte[] decrypt(String ciphertext) { DecryptRequest request = new DecryptRequest(); request.setCiphertextBlob(ciphertext); request.setProtocol(ProtocolType.HTTPS); try { DecryptResponse response = client.getAcsResponse(request); String plaintext = response.getPlaintext(); //注意:需要base64 decode return Base64.decodeBase64(plaintext); } catch (ClientException e) { e.printStackTrace(); return null; } } 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"); return null; } } } /** * 服務啟動 * * @throws IOException */ private void startService() throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); //監聽端口8888,能同時接受30個請求, 可自行更改端口 HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8888), 30); httpserver.createContext("/", new HlsDecryptHandler()); httpserver.start(); System.out.println("no token hls decrypt server started"); } }
Python代碼示例
Python SDK需要的依賴如下:
pip install aliyun-python-sdk-core
pip install aliyun-python-sdk-kms
pip install aliyun-python-sdk-mts
Python代碼示例如下:
# -*- coding: UTF-8 -*- from BaseHTTPServer import BaseHTTPRequestHandler from aliyunsdkcore.client import AcsClient from aliyunsdkkms.request.v20160120 import DecryptRequest import cgi import json import base64 import urlparse client = AcsClient("","",""); class AuthorizationHandler(BaseHTTPRequestHandler): def do_GET(self): self.check() self.set_header() cipertext = self.get_cihpertext() plaintext = self.decrypt_cihpertext(cipertext) print plaintext key = base64.b64decode(plaintext) print key self.wfile.write(key) def do_POST(self): pass def check(self): #check MtsHlsUriToken, etc. pass def set_header(self): self.send_response(200) #cors self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() def get_cihpertext(self): path = urlparse.urlparse(self.path) query = urlparse.parse_qs(path.query) return query.get('Ciphertext')[0] def decrypt_cihpertext(self, cipertext): request = DecryptRequest.DecryptRequest() request.set_CiphertextBlob(cipertext) response = client.do_action_with_exception(request) jsonResp = json.loads(response) return jsonResp["Plaintext"] if __name__ == '__main__': # Start a simple server, and loop forever from BaseHTTPServer import HTTPServer print "Starting server, use to stop" server = HTTPServer(('127.0.0.1', 8888), AuthorizationHandler) server.serve_forever()
調用媒體處理的查詢媒體-使用媒體ID接口獲取播放地址。
您可以通過開發者門戶直接調用API或將API集成到服務中調用。
播放加密視頻。
您可以使用自己的播放器或阿里云播放器播放加密視頻。
如果使用非阿里云播放器,請自行實現播放邏輯。
如果使用阿里云播放器,請按照阿里云播放器的要求獲取令牌和鑒權信息后播放。詳細介紹請參見視頻播放。
您也可以借助一個在線播放器,測試HLS加密視頻的播放。
以阿里云播放器用戶診斷工具為例,請將獲取的播放地址,填入對話框中,單擊視頻播放。
說明通過瀏覽器DEBUG,可以看到播放器自動請求了鑒權服務器,獲取解密密鑰,并進行解密播放。
使用阿里云播放器測試播放的內部流程解析如下:
獲取播放地址后,播放器把OSS域名替換為CDN域名,再拼接上參數MtsHlsUriToken作為請求解密密鑰的令牌向CDN獲請求播放地址。請求示例:
https://example.aliyundoc.com/test_01.m3u8?MediaId=fbbf98691ea44b7c82dd75c5bc8b****&MtsHlsUriToken=<業務方頒發的令牌>
。重要如果您使用的是阿里云播放器,則無需自行拼接MtsHlsUriToken。如果您使用的是其它播放器,則需要自行拼接。
收到請求后,阿里云CDN動態修改M3U8文件中的解密URI并返回修改后的地址給播放器,如原為
https://example.aliyundoc.com?Ciphertext=aabbccddeeff&MediaId=fbbf98691ea44b7c82dd75c5bc8b****
,修改后為https://example.aliyundoc.com?Ciphertext=aabbccddeeff&MediaId=fbbf98691ea44b7c82dd75c5bc8b****&MtsHlsUriToken=<業務方頒發的令牌>
。播放器訪問M3U8文件中EXT-X-KEY標簽中的URI以獲取解密密鑰,此URI為業務方搭建的解密密鑰接口。業務方調用KMS的Decrypt接口,并將獲取的明文密鑰Base64decode之后返回給播放器。播放器用數據密鑰(DK)去解密加密過的ts文件進行播放。