全部产品
Search
文档中心

视频点播:HLS标准加密

更新时间:May 06, 2025

HLS标准加密采用AES_128算法,能够适配所有HLS播放器,并需结合密钥管理与令牌服务,广泛应用于在线教育、独播剧等高安全需求领域。本文将介绍视频的HLS加密与解密播放方法。

工作原理

视频点播采用信封加密技术,业务方通过阿里云KMS生成数据密钥(DK)和信封密钥(EDK);使用DK加密视频后,同时存储加密视频和(EDK)。终端播放时通过解密服务获取(DK)进行视频解密播放。

如果您需要对解密地址进行安全验证,可以通过开启M3U8标准加密改写(默认改写参数名为MtsHlsUriToken)实现对HLS数据访问过程的再次加密保护。M3U8标准加密改写的开启方式及技术原理请参见配置M3U8标准加密改写

前提条件

  • 阿里云视频加密(HLS 标准加密)属于免费服务,但需通过视频转码实现加密功能。转码操作将产生服务费用,具体计费标准请参考媒资转码计费

  • 已经开通了视频点播服务。开通步骤请参见开通视频点播

  • 已经授权视频点播访问您密钥管理服务的权限,可以通过云资源访问授权页面进行授权。

  • 已在视频点播中配置加速域名,具体步骤请参见添加加速域名

  • 已安装视频点播服务端SDK,具体步骤请参见服务端SDK。本文以Java语言为例进行说明。

相关概念

概念

说明

访问控制RAM(Resource Access Management)

是阿里云提供的管理用户身份与资源访问权限的服务。更多信息,请参见访问控制

密钥管理服务KMS(Key Management Service)

是一站式密钥管理与数据加密服务平台,提供简单、可靠、安全、合规的加密保护与凭据管理能力。更多信息,请参见密钥管理服务

数据密钥DK(Data Key)

数据密钥是加密数据使用的明文数据密钥。更多信息,请参见基本概念

信封数据密钥EDK(Enveloped Data Key/Encrypted Data Key)

信封数据密钥是通过信封加密技术保密后的密文数据密钥。更多信息,请参见基本概念

加密及解密流程

上传及加密流程

视频安全-HLS标准加密3

解密播放流程

开启M3U8标准加密改写(推荐)

视频安全-HLS标准加密6..png

未开启M3U8标准加密改写

视频安全-HLS标准加密2

加密视频

  1. 上传视频并接收回调。

    为确保支持HLS标准加密的视频不被自动转码,在上传至视频点播时需要使用系统内置的不转码模板组进行上传,以防止上传时自动触发转码。

    1. 通过视频点播控制台上传,请参见通过视频点播控制台上传文件;通过服务端接口上传,请参见媒体上传

    2. 配置视频点播的事件通知,当接收到视频上传完成的回调消息时,则表明视频已经上传到视频点播中。事件通知的配置方法,请参见事件通知

  2. 搭建加密服务。

    1. 创建Service Key。

      Service Key是密钥管理服务中的主加密Key,必须用于生成标准加密密钥。未创建将导致调用GenerateKMSDataKey - 创建KMS数据密钥接口生成密钥时将会报错。

      1. 单击页面左上方的服务地域标识,切换到需要创建Service Key的服务地域下。

        说明

        创建的Service Key与视频存储的源站地域必须一致,例如:视频存储在华东2(上海),则Service Key必须在华东2(上海)创建。

        标准加密-服务地域

      2. 登录视频点播控制台,选择配置管理 > 媒体处理配置 > 标准加密

      3. 在标准加密页面,单击创建Service Key

        创建成功后,会提示您密钥(Service Key)创建成功,且在密钥信息区域可以查看您的Service Key。

        说明

        若已提示密钥创建成功,仍未看到Service Key,可能是由于服务角色缺失导致,您可以通过重新授权恢复角色后再刷新页面查看Service Key。

    2. 调用接口生成数据密钥。

      调用GenerateKMSDataKey - 创建KMS数据密钥接口生成数据密钥(DK)和信封数据密钥(EDK)时,无需传参。成功后,返回的CiphertextBlob即为AES_128格式的(EDK),需缓存此值用于标准加密转码。返回的Plaintext即为(DK)。

  3. 创建用于HLS标准加密的转码模板组。

    HLS标准加密过程中需要用到两个转码模板组:系统内置的不转码模板组和需要自行创建的HLS标准加密模板组。HLS标准加密模板组创建方法如下:

    1. 登录视频点播控制台,选择配置管理 > 媒体处理配置 > 转码模板组

    2. 在转码模板组页面创建用于HLS标准加密的模板组。

      其中,封装格式需设置为hls;在高级参数区域,必须开启视频加密,且加密方式勾选私有加密选项;其余参数您可以根据实际需要进行配置,详细的参数解释,请参见转码模板视频安全-HLS加密-控制台

    3. 模板创建完成后,您可以在转码模板组列表页查看转码模板组的ID,请保存该ID用于后续发起HLS标准加密转码时使用。视频安全-HLS加密-控制台1

  4. 发起HLS标准加密转码。

    1. 调用SubmitTranscodeJobs接口,发起HLS标准加密转码。

      展开查看Java示例代码

      下述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();
              }
          }
      }       
    2. 如果已经配置了视频点播的事件通知,当接收到单个清晰度转码完成全部清晰度转码完成的回调消息时,则表明视频已经转码完成。

  5. 查看HLS标准加密结果。

    转码完成后,您可以通过如下三种方式来判断标准加密是否成功。

    • 方式一:登录视频点播控制台,选择媒资库 > 音/视频 > 管理 > 视频地址,在视频地址页面,如果视频有多种格式的输出(例如还存在格式为mp4的原始文件),只需要查看m3u8格式的视频流是否带有标准加密字样,如果存在,则表明标准加密成功。视频安全-HLS标准加密5

    • 方式二:将带有标准加密字样的M3U8文件的地址拷贝出来,使用命令curl -v "M3U8文件地址",查看获取到的M3U8内容是否存在URI="<业务方在发起标准加密时传递的解密URI地址,即DecryptKeyUri参数值>"关键信息,有则表明为标准加密且加密成功。

    • 方式三:调用GetTranscodeTask - 查询转码任务详情接口,传入步骤四中返回参数 JobId 的值。若返回参数TranscodeTemplateId为步骤三中创建的转码模板组ID且TranscodeJobStatus为转码成功,则表明为标准加密且加密成功。

HLS(M3U8)标准加密改写(推荐)

开启M3U8标准加密改写功能后,系统会在HLS协议的Media Playlist(M3U8文件)中#EXT-X-KEY标签后自动添加加密参数(如加密算法、密钥URI及鉴权参数)。客户端解析改写后的M3U8文件时,将使用带鉴权参数的密钥URI请求密钥,通过密钥和指定算法解密TS分片,从而实现HLS流媒体的加密访问保护

步骤一:开启标准加密参数透传

在视频点播控制台开启HLS标准加密参数透传

开启HLS标准加密参数透传后,可改写HLS协议的M3U8文件,具体为修改#EXT-X-KEY标签中的URI,在其末尾追加客户端请求携带的参数,默认参数名为MtsHlsUriToken。

前提条件

已配置跨域访问,详情请参见配置跨域资源共享

重要

政务云暂不支持该功能。

操作步骤

  1. 登录点播控制台

  2. 在左侧导航栏,单击配置管理

  3. 单击分发加速配置 > 域名管理,进入域名管理页面。

  4. 在指定域名右侧的操作列,单击配置

  5. 在指定域名的左侧导航栏,单击视频相关

  6. 在加密播放区域,开启HLS标准加密参数透传开关p181836

说明
  • 开启后,HLS标准加密参数透传时,将通过改写Token鉴权信息参数帮您鉴权。改写的参数名称为MtsHlsUriToken参数的值为test。当CDN解密播放时,会将MtsHlsUriToken=test追加到M3U8文件中#EXT-X-KEY标签的URI末尾。

步骤二:携带MtsHlsUriToken参数请求

客户端携带MtsHlsUriToken参数向CDN节点发起对M3U8文件的访问请求。

其中,MtsHlsUriToken需要您自行搭建令牌服务,颁发用户令牌(即生成MtsHlsUriToken)。

下述代码所生成的Token即是MtsHlsUriToken。下述Java示例代码中,您需要按实际情况变更的参数如下:

展开查看Java示例代码

参数

传入值

ENCRYPT_KEY

加密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;
    }}
                        

步骤三:解密播放文件

CDN节点收到客户端请求后,鉴权通过则解密播放文件。

若上述步骤二中生成的MtsHlsUriToken参数值为test,则当CDN解密播放时,会将MtsHlsUriToken=test追加到M3U8文件中#EXT-X-KEY标签的URI末尾。

具体的鉴权校验逻辑您需要自行实现,可以参考播放视频中开启M3U8标准加密改写方式的解密服务的示例代码。

播放视频

开启M3U8标准加密改写(推荐)

  1. 搭建解密服务。

    搭建一个本地HTTP服务,用于解密视频。

    调用DecryptKMSDataKey - 解密KMS数据密钥密文接口进行解密,该接口返回的数据密钥(DK)即PlainTextGenerateKMSDataKey - 创建KMS数据密钥接口返回的PlainText经过base64_decode之后的数据。

    如果您需要对解密地址进行安全验证,可以通过开启M3U8标准加密改写(默认改写的参数名为MtsHlsUriToken)实现对HLS数据访问过程的再次加密保护。根据您是否开启M3U8标准加密改写,搭建解密服务的代码实现不同,具体如下:

    展开查看Java示例代码

    下述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.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;
                }
            }
            /**
             * 从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;
                }
            }
            
              /**
             * 校验令牌有效性 M3U8标准加密改写必备
             * @param token
             * @return
             */
            private boolean validateToken(String token) {
                if (null == token || "".equals(token)) {
                    return false;
                }
                //TODO 业务方实现令牌有效性校验
                return true;
            }
            /**
             * 获取Token参数 M3U8标准加密改写必备
             *
             * @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();
        }}
  2. 获取视频播放地址和凭证。

    调用GetVideoPlayAuth - 获取音视频播放凭证接口获取播放凭证或调用GetPlayInfo - 获取音视频播放地址接口获取播放地址。

  3. 播放加密视频。

    HLS标准加密支持所有的HLS播放器,您可以选择使用自研的播放器或阿里云播放器进行播放加密视频。

    如果使用阿里云播放器,请按照阿里云播放器的要求获取令牌和鉴权信息后播放,详情请参见如何播放加密视频。如果使用非阿里云播放器,请自行实现播放逻辑。

    使用阿里云播放器测试播放的内部流程解析如下:

    开启M3U8标准加密改写(推荐)

    流程

    • 播放器解析M3U8文件中的EXT-X-KEY标签,获取解密密钥URI,该URI对应加密配置 EncryptConfig中的DecryptKeyUri参数值。

    • 为限制非法访问,播放器需携带认证信息(通过MtsHlsUriToken参数传递)请求解密接口。

    • 播放器自动请求解密接口获取密钥,解密加密的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标准加密改写

  1. 搭建解密服务。

    搭建一个本地HTTP服务,用于解密视频。

    调用DecryptKMSDataKey - 解密KMS数据密钥密文接口进行解密,该接口返回的数据密钥(DK)即PlainTextGenerateKMSDataKey - 创建KMS数据密钥接口返回的PlainText经过base64_decode之后的数据。

    具体示例如下:

    展开查看Java示例代码

    下述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();
        }}
    
  2. 获取视频播放地址和凭证。

    调用GetVideoPlayAuth - 获取音视频播放凭证接口获取播放凭证或调用GetPlayInfo - 获取音视频播放地址接口获取播放地址。

  3. 播放加密视频。

    HLS标准加密支持所有的HLS播放器,您可以选择使用自研的播放器或阿里云播放器进行播放加密视频。

    如果使用阿里云播放器,请按照阿里云播放器的要求获取令牌和鉴权信息后播放,详情请参见如何播放加密视频。如果使用非阿里云播放器,请自行实现播放逻辑。

    使用阿里云播放器测试播放的内部流程解析如下:

    未开启M3U8标准加密改写

    流程

    • 播放器解析M3U8文件中的EXT-X-KEY标签,获取解密密钥URI,该URI对应加密配置 EncryptConfig中的DecryptKeyUri参数值。

    • 播放器自动请求解密接口获取密钥,解密加密的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****

相关文档