HLS标准加密

视频加密旨在通过对视频内容进行深度安全处理,确保视频数据不被非法获取和传播,可有效防止视频泄露和盗链问题,广泛用于在线教育及财经等对内容安全性要求高的领域。阿里云目前支持两种加密方式:阿里云私有加密(推荐)和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

加密流程

媒体处理的视频加密流程如下:

mts_wf_hls_encrypt

  1. 业务方开通阿里云媒体处理服务(MPS)、存储服务(OSS)、访问控制(RAM)、密钥管理服务(KMS)以及内容分发网络(CDN)(如未开通)。

  2. 业务方授权媒体处理访问刚开通的阿里云密钥管理服务(KMS)。

    说明

    授权访问是为了媒体处理在加密视频阶段调用KMS的GenerateDataKey接口生成数据密钥(DK)和信封数据密钥(EDK)。

  3. 业务方配置OSS输出Bucket域名为CDN加速域名,配置OSS域名CNAME及回源Host。

  4. 业务方创建视频加密工作流,传入OSS输出Bucket、Key URI等关键信息。

    Key URI是业务方的服务地址。媒体处理完成视频加密后的M3U8文件中会包含Key URI信息。

  5. 业务方上传需要加密的视频,上传过程中指定创建好的视频加密工作流。

  6. 视频上传完成后媒体处理自动触发加密转码。

    媒体处理调用GenerateDataKey接口生成数据密钥(DK)和信封数据密钥(EDK),并使用数据密钥(DK)加密视频。加密完成后,将业务方提供的Key URI与信封数据密钥(EDK)写入M3U8文件。

  7. 媒体处理将M3U8文件及ts文件存入OSS的输出Bucket中。

解密流程

播放端解密播放HLS加密视频的流程如下:

mts_hls_decrypt

  1. 业务方搭建令牌服务,用于颁发令牌MtsHlsUriToken。

    重要

    令牌服务指用于派发MtsHlsUriToken的服务。

  2. 业务方调用KMS解密接口搭建解密服务,用于解密视频,同时提供数据密钥(DK)给播放终端。

    重要

    KMS返回Base64加密后的数据密钥给业务方。业务方需要将调用KMS接口获得的数据密钥Base64 Decode之后返回给播放终端。

  3. 业务方调用MPS的QueryMediaList接口获取视频M3U8文件的OSS地址,并将地址拼接MtsHlsUriToken后返回给播放终端。

  4. 播放终端携带MtsHlsUriToken、数据密钥向阿里云CDN请求播放地址,阿里云CDN改写M3U8文件,将业务方的Key URI与信封加密密钥返回播放终端。播放终端解密播放视频。

业务方实现概览

在完整的HLS加密视频和解密播放流程中,需要业务方自行实现的代码逻辑包括:

  1. 创建加密工作流。

    说明

    尽管控制台也可以创建加密工作流,但为了更完整高效地使用HLS标准加密服务,建议您集成服务端SDK后创建。

  2. 搭建颁发及验证MtsHlsUriToken令牌服务,并校验解密令牌。推荐一个令牌只允许使用一次。

  3. 调用KMS服务的解密接口搭建解密服务,并将调用KMS接口获得的明文密钥Base64 Decode之后返回播放终端。

前提条件

接入媒体处理的HLS加密功能需要完成以下准备:

  1. 开通相关阿里云服务。

    如果您尚未开通MPS,OSS,KMS, RAM,CDN服务,请先开通服务。

    1. 开通MPS服务。具体操作,请参见开通MPS服务

    2. 开通OSS服务。具体操作,请参见开通OSS服务

    3. 开通KMS服务。具体操作,请参见开通KMS服务

    4. 开通RAM服务并授权。具体操作,请参见创建RAM用户并授权

    5. 开通CDN服务。具体操作,请参见开通CDN服务

  2. 授权媒体处理访问KMS。

    1. 登录RAM访问控制台

    2. 单击授权进入新增授权页面。

    3. 授权主体搜索框搜索AliyunMtsDefaultRole,选择系统创建的可供媒体处理使用的角色。

    4. 权限策略下方的搜索框搜索KMS并选择AliyunKMSFullAccess,然后单击确认新增授权

    完成授权后,媒体处理可以访问您的KMS服务。收到加密视频请求时,媒体处理调取KMS接口获取数据密钥加密视频。

  3. 配置OSS输出Bucket域名及回源Host。具体操作,请参见配置加速域名。如已配置请忽略。

    说明

    OSS域名可以手动输入阿里云OSS Bucket的外网域名(OSS外网域名可前往OSS控制台查看),如:exampleBucket****.oss-cn-hangzhou.aliyuncs.com ,也可以直接选择同账号下需要加速的OSS Bucket。不支持OSS内网域名。

加密视频

请按以下指引完成视频加密:

  1. 创建加密工作流。

    创建加密工作流需要集成阿里云SDK并添加媒体处理相关依赖。请根据您使用的开发语言选择查看对应代码示例。

    重要

    创建加密工作流时必须传入业务方的Key URI地址,媒体处理加密视频时将该地址写入M3U8文件存储到OSS。Key URI示例:example.aliyundoc.com

    语言

    集成SDK

    加密工作流代码示例

    Java

    Java SDK安装

    创建HLS标准加密工作流

    Python

    Python SDK安装

    创建HLS标准加密工作流

    PHP

    PHP SDK安装

    创建HLS标准加密工作流

    Node.js

    Node.js SDK安装

    创建HLS标准加密工作流

  2. 上传视频触发加密转码。您可以通过媒体处理控制台或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加密视频

请按以下指引完成视频解密播放:

  1. 搭建令牌服务。

    说明

    令牌服务需要根据您的加密逻辑自行搭建,以达到更高的视频安全等级。

  2. 搭建解密服务。

    搭建一个本地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()
  3. 调用媒体处理的查询媒体-使用媒体ID接口获取播放地址。

    您可以通过开发者门户直接调用API或将API集成到服务中调用。

  4. 播放加密视频。

    您可以使用自己的播放器或阿里云播放器播放加密视频。

    • 如果使用非阿里云播放器,请自行实现播放逻辑。

    • 如果使用阿里云播放器,请按照阿里云播放器的要求获取令牌和鉴权信息后播放。详细介绍请参见视频播放

    您也可以借助一个在线播放器,测试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文件进行播放。

相关文档

阿里云私有加密