产品百科 |HLS 标准加密安全播放

简介: 为了加强标准加密视频在解密播放时解密秘钥的安全性,业务方需要同时提供令牌服务和解密服务,其中令牌服务生成鉴权令牌,解密服务用于验证令牌和获取解密秘钥。

简介

为了加强标准加密视频在解密播放时解密秘钥的安全性,业务方需要同时提供令牌服务和解密服务,其中令牌服务生成鉴权令牌,解密服务用于验证令牌和获取解密秘钥。

说明

  • 令牌是被 CDN 改写到解密接口上,所以在使用标准加密前必须先提工单开启 CDN 域名的令牌改写功能。
  • 非 CDN 播放地址,不支持处理令牌参数。

基本原理

标准加密视频的解密播放时,令牌生成以及令牌校验需要业务方封装服务,其他逻辑过程都封装在阿里云播放器中,基本原理如下:

说明 如是非阿里云播放器,需要业务方自行封装解密播放处理过程。

image.png

准备条件

标准加密视频在进行解密播放时,需要业务方提供令牌服务和解密服务,其中解密服务是用于获取视频的解密秘钥,而令牌服务是用于生成鉴权令牌。

令牌服务

生成令牌时,可以通过业务方用户 ID、视频播放终端 (web、ios、android)、视频 ID 等信息按照一定方式生成令牌。

说明

  • 标准播放器在播放视频时只会请求一次解密秘钥,所以令牌最好只能使用一次且具备过期时间等特性。
  • 令牌生成校验请参见令牌示例代码

解密服务

解密服务主要对调用方进行鉴权和返回解密秘钥。

  • 根据传递的令牌参数,判断令牌的有效性,如:是否是令牌服务生成、令牌是否过期、是否已经被使用过等。
  • 如果令牌校验通过,则根据密文秘钥获取到明文秘钥并将明文秘钥通过 base64decode 后返回。

说明

  • 此处鉴权主要是指校验令牌的有效性,且令牌只能通过 MtsHlsUriToken 参数传递到解密服务。
  • 解密服务请参见解密服务示例代码

实现过程

标准加密视频在解密播放时会经历以下几个处理阶段:

说明

  • 此处主要解析 阿里云播放器 在标准加密视频解密播放的处理逻辑。
  • 非阿里云播放器需要自行封装请求播放服务过程,获取到加密视频地址后直接给播放器进行解密播放即可,详细请参见 GetPlayInfo
  1. 业务方需要先调用令牌服务颁发令牌 Token。
  2. 令牌服务根据传递的信息生成令牌 Token。
  3. 令牌服务将生成的令牌 Token 返回给调用方。
    说明 以上步骤不属于 阿里云播器 处理过程,需要业务方单独调用令牌服务生成令牌 Token 并传递给播放器,而使用的是非阿里云播放器,那么业务方可选择将该步集成到播放器播放处理逻辑中。
  4. 调用方通过播放器提供的 PlayConfig 参数,将令牌 Token 设置到 MtsHlsUriToken 上。
  5. 播放器获取设置的 MtsHlsUriToken 参数、播放的视频 ID 等信息,从播放服务获取播放地址。说明
  1. 播放器获取到标准加密 m3u8 地址并请求 m3u8。
  2. CDN 改写 m3u8 内容,将 MtsHlsUriToken 改写到解密接口上。例如:image.png
  3. 播放器解析 m3u8 内容并获取到解密接口并请求获取解密秘钥。例如:http://demo.com/ddf56e501d07402796c468bbea08ec8c/9e712e72879b93f8933d5f9eca4bacaa-fd-encrypt-stream.m3u8?MtsHlsUriToken=NWItZGU5ZWEwODRlMzky
  4. 解密服务接收到请求,调用令牌接口校验令牌 Token 的有效性。
  5. 令牌 Token 校验通过,则将明文秘钥通过 base64decode 解码后再后返回给播放器。
  6. 播放器在获取到解密秘钥后,将对标准加密视频进行解密播放。
    说明 说明:通用播放器在播放视频时,只会在解密播放前调用一次解密接口获取秘钥,后续解密播放过程不会再次请求解密接口。

令牌示例代码

本示例代码主要是对 令牌生成 和 令牌校验 相关实现的模板代码,不作为实际部署代码。

说明

  • 以下代码仅仅提供令牌 Token 生成和校验逻辑的一个范例模板,不作为实际部署代码用。
  • 该示例代码的令牌生成采用简单的 AES 加密生成,业务方也可自行实现其他生成方式。
  • 关于生成的令牌 Token 的存储、获取由业务方自行实现。

public class PlayToken {
    //非AES生成方式,无需以下参数
    private static String ENCRYPT_KEY = "";
    private static String INIT_VECTOR = "";
    /**
     * 根据传递的参数生成令牌
     * 说明:
     *  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;
        //生成token
        String token = encrypt(base, ENCRYPT_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);
        //先校验token的有效时间
        Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1));
        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 key
     * @param value
     * @return
     * @throws Exception
     */
    public String encrypt(String key, String value) throws Exception {
        IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, e);
        byte[] encrypted = cipher.doFinal(value.getBytes());
        return Base64.encodeBase64String(encrypted);
    }
    /**
     * AES解密token
     *
     * @param key
     * @param encrypted
     * @return
     * @throws Exception
     */
    public String decrypt(String key, String encrypted) throws Exception {
        IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, e);
        byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
        return new String(original);
    }
    /**
     * Token信息,业务方可提供更多信息,这里仅仅给出示例
     */
    class Token {
        //Token的有效使用次数,分布式环境需要注意同步修改问题
        int useCount;
        //token内容
        String token;
    }}

解密服务示例代码

说明 以下代码可直接运行启动,但仅仅作为测试使用,不可作为线上正式部署。

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.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的授权AK信息
        String accessKeyId = "<Your AccessKeyId>";
        String accessKeySecret = "<Your AccessKeySecrect>";
        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)) {
                //校验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);
                //返回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) {
            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;
            }
        }
        /**
         * 校验令牌有效性
         * @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();
        //监听端口9999,能同时接受30个请求
        HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(9999), 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();
    }}


「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

image.png

相关文章
|
1月前
|
存储 安全 API
如何对 API 进行安全加密?
对API进行安全加密是保障数据安全和系统稳定的重要措施
148 56
|
2月前
|
安全 网络安全 区块链
网络安全与信息安全:构建数字世界的防线在当今数字化时代,网络安全已成为维护个人隐私、企业机密和国家安全的重要屏障。随着网络攻击手段的不断升级,从社交工程到先进的持续性威胁(APT),我们必须采取更加严密的防护措施。本文将深入探讨网络安全漏洞的形成原因、加密技术的应用以及提高公众安全意识的重要性,旨在为读者提供一个全面的网络安全知识框架。
在这个数字信息日益膨胀的时代,网络安全问题成为了每一个网民不可忽视的重大议题。从个人信息泄露到企业数据被盗,再到国家安全受到威胁,网络安全漏洞如同隐藏在暗处的“黑洞”,时刻准备吞噬掉我们的信息安全。而加密技术作为守护网络安全的重要工具之一,其重要性不言而喻。同时,提高公众的安全意识,也是防范网络风险的关键所在。本文将从网络安全漏洞的定义及成因出发,解析当前主流的加密技术,并强调提升安全意识的必要性,为读者提供一份详尽的网络安全指南。
|
3月前
|
存储 SQL 安全
网络安全与信息安全:守护数字世界的坚盾在这个高度数字化的时代,网络安全和信息安全已经成为个人、企业乃至国家安全的重要组成部分。本文将深入探讨网络安全漏洞、加密技术以及安全意识的重要性,旨在为读者提供一个全面的网络安全知识框架。
随着互联网技术的飞速发展,网络安全问题日益凸显。从个人信息泄露到企业数据被盗,再到国家安全受到威胁,网络安全事件层出不穷。本文将从网络安全漏洞的定义与分类入手,探讨常见的网络攻击手段;随后深入解析加密技术的原理及其在保护信息安全中的作用;最后强调提升公众与企业的安全意识的重要性,并提出具体的建议。通过综合运用这些知识点,我们可以更好地构建起一道道坚固的防线,守护我们的数字世界。
|
2月前
|
存储 安全 5G
|
2月前
|
安全 算法 网络安全
网络安全与信息安全:守护数字世界的坚盾在这个高度数字化的时代,网络安全和信息安全已成为全球关注的焦点。无论是个人隐私还是企业数据,都面临着前所未有的风险和挑战。本文将深入探讨网络安全漏洞、加密技术以及安全意识的重要性,旨在为读者提供实用的知识,帮助构建更加安全的网络环境。
【10月更文挑战第4天】 在数字化浪潮中,网络安全与信息安全成为不可忽视的议题。本文通过分析网络安全漏洞的类型与成因,探讨加密技术的原理与应用,并强调提升安全意识的必要性,为读者提供一套全面的网络安全知识框架。旨在帮助个人和企业更好地应对网络威胁,保护数字资产安全。
146 65
|
2月前
|
存储 安全 前端开发
端到端加密:确保数据传输安全的最佳实践
【10月更文挑战第12天】端到端加密(E2EE)是确保数据传输安全的重要手段,通过加密技术保障数据在传输过程中的隐私与完整性,防止第三方窃听和篡改。本文介绍E2EE的工作原理、核心优势及实施步骤,并探讨其在即时通讯、文件共享和金融服务等领域的应用,强调了选择加密协议、密钥管理、数据加密及安全接口设计的重要性,旨在帮助企业和开发者有效保护用户数据,满足数据保护法规要求。
|
2月前
|
安全 数据安全/隐私保护 CDN
阿里云国际站:海外视频安全的DRM
阿里云国际站:海外视频安全的DRM加密
|
2月前
|
安全 数据安全/隐私保护 CDN
阿里云海外视频安全的DRM
阿里云海外视频安全的DRM加密
|
3月前
|
人工智能 供应链 安全
网络安全与信息安全:构建数字世界的坚固防线在当今数字化时代,网络安全已成为维护个人隐私、企业机密和国家安全的重要基石。本文旨在探讨网络安全漏洞、加密技术及安全意识等关键领域,通过深入浅出的方式,引导读者理解网络安全的核心要素,并分享实用的防护策略,共同守护我们的数字世界。
随着互联网技术的飞速发展,网络安全威胁日益凸显,成为全球关注的焦点。本文聚焦网络安全的三大核心议题——网络安全漏洞、加密技术与安全意识,旨在揭示它们之间的相互关联与重要性。通过剖析真实案例,展现网络攻击的复杂性与破坏力;解析加密技术的原理与实践,强调其在保护数据安全中的关键作用;同时,倡导提升公众安全意识,构建多层次的网络安全防护体系。本文不仅为专业人士提供技术参考,也旨在提高普罗大众的网络安全认知,共同筑牢数字世界的安全防线。
181 10
|
3月前
|
SQL 安全 算法
网络安全与信息安全的守护之道在数字化时代,网络安全和信息安全已成为企业和个人不可忽视的重要议题。本文将探讨网络安全漏洞、加密技术以及安全意识等方面的知识,帮助您建立更安全的网络环境。
随着互联网技术的飞速发展,网络安全问题日益凸显,如何保护个人及企业的敏感信息成为亟待解决的难题。本文从网络安全漏洞、加密技术和安全意识三个方面展开,详细介绍了当前面临的主要安全威胁及应对策略,旨在提升公众的安全意识和防护能力。
47 1