HLS数据加密

视频加密旨在通过对视频内容进行深度安全处理,确保视频数据不被非法获取和传播,可有效防止视频泄露和盗链问题,广泛用于在线教育及财经等对内容安全性要求高的领域。阿里云目前支持两种加密方式:阿里云私有加密(推荐)和HLS标准加密。本文介绍媒体处理HLS加密的原理和接入流程,帮助用户更好的理解和实施HLS加密,在保证视频安全的同时,实现流畅的在线播放体验。

使用场景

  • HLS标准数据加密适用于对视频进行保护的场景,可以防止非法下载和非法传播。

  • 如果对安全有强需求,请开启工作流中的数据加密。详情参见 数据加密

使用说明

  • HLS标准数据加密需要使用SubmitJobs接口。

  • 工作流中不可使用HLS标准数据加密Base64方式。

示例代码依赖

  • MPS SDK详情参见 安装

  • 其他依赖:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.68.noneautotype</version>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.9</version>
    </dependency>

加密示例代码(Base64方式)

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.mps.sdk.utils.InitClient;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.mts.model.v20140618.SubmitJobsRequest;
import com.aliyuncs.mts.model.v20140618.SubmitJobsResponse;
import org.apache.commons.codec.binary.Base64;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * *****   使用须知     ******
 * 本demo为Base64方式提交标准加密转码示例, 解密播放需结合Base64方式解密服务使用, 参考示例 Base64DecryptServer
 * KeyUri为您的解密服务地址, 请确保可用性, 否则会导致解密播放失败
 */
public class Base64EncryptionTranscode {

    //管道ID, 可以在控制台 【全局设置】->【管道】查看
    private static String pipelineId = "b******fe40a0cbf";
    //模板ID, 预置模板参考 https://help.aliyun.com/document_detail/29256.html
    private static String templateId = "S00000001-100020";

    private DefaultAcsClient client = null;

    public Base64EncryptionTranscode() throws ClientException {

        this.client = InitClient.initMpsClient();
    }

    public static void main(String[] args) throws ClientException {
        Base64EncryptionTranscode base64EncryptionTranscode = new Base64EncryptionTranscode();
        base64EncryptionTranscode.submitJobs();
    }

    /**
     * 提交转码
     * @throws ClientException
     */
    private void submitJobs() throws ClientException {

        JSONObject inputFile = new JSONObject();
        inputFile.put("Location", "oss-cn-beijing");
        inputFile.put("Bucket", "<your bucket name>");
        try {
            inputFile.put("Object", URLEncoder.encode("mps-test/demo/test.mp4", "utf-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("input URL encode failed");
        }

        SubmitJobsRequest request = new SubmitJobsRequest();
        request.setInput(inputFile.toJSONString());
        request.setInput(inputFile.toJSONString());
        request.setOutputLocation("oss-cn-beijing");
        request.setOutputBucket("<your bucket name>"");

        JSONArray outputs = new JSONArray();

        JSONObject output = new JSONObject();
        //输出文件路径,m3u8无需填写后缀,最终生成 hls-test.m3u8
        //注意避免输出到工作流触发路径
        output.put("OutputObject", "mps-test/demo-v2/hls-test");
        output.put("TemplateId", templateId);
        output.put("Encryption", getEncryptionConfigs());

        outputs.add(output);

        request.setOutputs(outputs.toJSONString());
        request.setPipelineId(pipelineId);

        SubmitJobsResponse response = client.getAcsResponse(request);
        System.out.println("RequestId is:" + response.getRequestId());
        System.out.println("JobId is:" + response.getJobResultList().get(0).getJob().getJobId());
        System.out.println("Response is:" + JSON.toJSONString(response));
    }

    /**
     * 构建加密配置
     * @return
     * @throws ClientException
     */
    private JSONObject getEncryptionConfigs() throws ClientException  {
        JSONObject encryption = new JSONObject();
        encryption.put("Type", "hls-aes-128");
        //加密字符串必须是16位
        encryption.put("Key", Base64.encodeBase64URLSafeString("encryptionkey128".getBytes()));
        //url为您的解密服务地址
        String url = "http://127.0.0.1:8888";
        encryption.put("KeyUri", Base64.encodeBase64URLSafeString(url.getBytes()));
        encryption.put("KeyType", "Base64");
        return encryption;
    }



}

加密示例代码(KMS方式)

KMS方式加密需要先创建HLS加密工作流,触发工作流后,正常解密播放后才能使用接口进行加密。请参见:如何进行HLS的加密与播放

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.mps.sdk.utils.InitClient;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.kms.model.v20160120.GenerateDataKeyRequest;
import com.aliyuncs.kms.model.v20160120.GenerateDataKeyResponse;
import com.aliyuncs.mts.model.v20140618.SubmitJobsRequest;
import com.aliyuncs.mts.model.v20140618.SubmitJobsResponse;
import org.apache.commons.codec.binary.Base64;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * *****   使用须知     ******
 * 本demo为KMS方式提交标准加密转码示例, 解密播放需结合KMS方式解密服务使用, 参考示例 HlsDecryptServerNoToken / HlsDecryptServer
 * 如果您是首次使用KMS方式标准加密, 请使用标准加密工作来完成首次转码
 * KeyId为您的KMS密钥, 通过首次执行标准加密工作流来自动创建, 后续您可以在kms控制台查看别名为alias/acs/mts的密钥
 * KeyUri为您的解密服务地址, 请确保可用性, 否则会导致解密播放失败
 */
public class KMSEncryptionTranscode {

    //管道ID, 可以在控制台 【全局设置】->【管道】查看
    private static String pipelineId = "b********40a0cbf";
    //模板ID, 预置模板参考 https://help.aliyun.com/document_detail/29256.html
    private static String templateId = "S00000001-100020";

    private DefaultAcsClient client = null;

    public KMSEncryptionTranscode() throws ClientException {

        this.client = InitClient.initMpsClient();
    }

    public static void main(String[] args) throws ClientException {
        KMSEncryptionTranscode kmsEncryptionTranscode = new KMSEncryptionTranscode();
        kmsEncryptionTranscode.submitJobs();
    }

    /**
     * 提交转码
     * @throws ClientException
     */
    private void submitJobs() throws ClientException {

        JSONObject inputFile = new JSONObject();
        inputFile.put("Location", "oss-cn-beijing");
        inputFile.put("Bucket", "<your bucket name>"");
        try {
            inputFile.put("Object", URLEncoder.encode("mps-test/demo/test.mp4", "utf-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("input URL encode failed");
        }
        SubmitJobsRequest request = new SubmitJobsRequest();
        request.setInput(inputFile.toJSONString());
        request.setOutputLocation("oss-cn-beijing");
        request.setOutputBucket("bole-test0727");

        JSONArray outputs = new JSONArray();

        JSONObject output = new JSONObject();
        //输出文件路径,m3u8无需填写后缀,最终生成 hls-test.m3u8
        //注意避免输出到工作流触发路径
        output.put("OutputObject", "mps-test/demo-v2/hls-test");
        output.put("TemplateId", templateId);
        output.put("Encryption", getEncryptionConfigs());

        outputs.add(output);
        request.setOutputs(outputs.toJSONString());
        request.setPipelineId(pipelineId);

        SubmitJobsResponse response = client.getAcsResponse(request);
        System.out.println("RequestId is:" + response.getRequestId());
        System.out.println("JobId is:" + response.getJobResultList().get(0).getJob().getJobId());
        System.out.println("Response is:" + JSON.toJSONString(response));
    }

    /**
     * 构建加密配置
     * @return
     * @throws ClientException
     */
    private JSONObject getEncryptionConfigs() throws ClientException {
        GenerateDataKeyRequest generateDataKeyRequest = new GenerateDataKeyRequest();
        //需换成KMS控制台的别名为alias/acs/mts的密钥。KMS控制台地址:https://kms.console.aliyun.com/cn-shanghai/key/list
        generateDataKeyRequest.setKeyId("eebc***********eb3");
        generateDataKeyRequest.setKeySpec("AES_128");
        GenerateDataKeyResponse generateDataKeyResponse = client.getAcsResponse(generateDataKeyRequest);
        JSONObject encryption = new JSONObject();
        encryption.put("Type", "hls-aes-128");
        encryption.put("Key", generateDataKeyResponse.getCiphertextBlob());
        String url = "http://127.0.0.1:8888?Ciphertext=" + generateDataKeyResponse.getCiphertextBlob();  // 解密地址,按需配置
        encryption.put("KeyUri", Base64.encodeBase64URLSafeString(url.getBytes()));
        encryption.put("KeyType", "KMS");
        return encryption;
    }
}

            

解密示例代码(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();
        //监听端口8888,能同时接受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的校验逻辑
 * 什么是标准加密改写 以及 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");
    }

}

 

相关文档

加密