使用场景

  • HLS标准数据加密适用于“对视频进行简单的保护”的场景,可以简单的防止非法下载和非法传播。
  • 如果对安全有强需求,请开启工作流中的数据加密。详情参见 数据加密

使用限制

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

示例代码依赖

  • MPS SDK详情参见 安装
  • 其他依赖。
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.25</version
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.9</version>
    </dependency>

加密示例代码(Base64方式)

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.mediaprocess.mpsDocTestShanghai.Config;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.mts.model.v20140618.SubmitJobsRequest;
import com.aliyuncs.mts.model.v20140618.SubmitJobsResponse;
import com.aliyuncs.profile.DefaultProfile;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class DataEncryptionBasicBase64 {
    private static String accessKeyId = "***"; // RAM账号的AccessKey ID
    private static String accessKeySecret = "***"; // RAM账号Access Key Secret
    private static String mpsRegionId="cn-shanghai"; // 地域ID
    private DefaultAcsClient client = null;
    private final String location = "oss-cn-shanghai"; // 按需配置
    private final String ossInputBucket = "huadong2-test"; // 按需配置
    private final String ossOutputBucket = "huadong2output"; // 按需配置
    private final String pipelineId = "f24c8a87a497456898b467892eea84d6"; // 按需配置
    public DataEncryptionBasicBase64() throws ClientException {
        DefaultProfile profile = DefaultProfile.getProfile(
                mpsRegionId,      // 地域ID
                accessKeyId,      // RAM账号的AccessKey ID
                accessKeySecret); // RAM账号Access Key Secret
        this.client = new DefaultAcsClient(profile);
    }
    private JSONObject getInputFile() {
        JSONObject inputFile = new JSONObject();
        inputFile.put("Location", location);
        inputFile.put("Bucket", ossInputBucket);
        try {
            inputFile.put("Object", URLEncoder.encode("wb-ceshi/487_93.mp4", "utf-8"));
        }catch (UnsupportedEncodingException e) {
                throw new RuntimeException("input URL encode failed");
            }
        return inputFile;
    }
    private JSONArray getOutputs()  throws ClientException{
        JSONArray outputs = new JSONArray();
        outputs.add(getOutput());
        return outputs;
    }
    private JSONObject getOutput()  throws ClientException{
        JSONObject output = new JSONObject();
        output.put("OutputObject", "wb-ceshi/202011131343");
        output.put("TemplateId", "S00000001-100000");
        output.put("Encryption", getEncryptionConfigs());
        return output;
    }
    private JSONObject getEncryptionConfigs() throws ClientException  {
        JSONObject encryption = new JSONObject();
        encryption.put("Type", "hls-aes-128");
        // 加密字符串必须是16位
        encryption.put("Key", Base64.encodeBase64URLSafeString("encryptionkey128".getBytes()));
        String url="http://127.0.0.1:8888/";
        encryption.put("KeyUri", Base64.encodeBase64URLSafeString(url.getBytes()));
        encryption.put("KeyType", "Base64");
        return encryption;
    }
    private String submitJobs() throws ClientException {
        JSONObject inputFile = getInputFile();
        SubmitJobsRequest request = new SubmitJobsRequest();
        request.setInput(inputFile.toJSONString());
        request.setOutputLocation(location);
        request.setOutputBucket(ossOutputBucket);
        request.setOutputs(getOutputs().toJSONString());
        request.setPipelineId(pipelineId);
        SubmitJobsResponse reponse = this.client.getAcsResponse(request);
        System.out.println(JSONObject.toJSONString(reponse.getJobResultList()));
        return reponse.getJobResultList().get(0).getJob().getJobId();
    }
    public static void main(String[] args) throws ClientException {
        DataEncryptionBasicBase64 demo = new DataEncryptionBasicBase64();
        String jobId= demo.submitJobs();
        System.out.println(jobId);
    }
}

加密示例代码(KMS方式)

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

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.mediaprocess.mpsDocTestShanghai.Config;
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 com.aliyuncs.profile.DefaultProfile;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class DataEncryptionBasicKMS { 
    private static String accessKeyId = "***"; // RAM账号的AccessKey ID
    private static String accessKeySecret = "***"; // RAM账号Access Key Secret
    private static String mpsRegionId="cn-shanghai"; // 地域ID
    private DefaultAcsClient client = null;
    private final String location = "oss-cn-shanghai"; // 按需配置
    private final String ossInputBucket = "huadong2-test"; // 按需配置
    private final String ossOutputBucket = "huadong2output"; // 按需配置
    private final String pipelineId = "f24c8a87a497456898b467892eea84d6"; // 按需配置
    public DataEncryptionBasicKMS() throws ClientException {
        DefaultProfile profile = DefaultProfile.getProfile(
                mpsRegionId,      // 地域ID
                accessKeyId,      // RAM账号的AccessKey ID
                accessKeySecret); // RAM账号Access Key Secret
        this.client = new DefaultAcsClient(profile);
    }
    private JSONObject getInputFile() {
        JSONObject inputFile = new JSONObject();
        inputFile.put("Location", location);
        inputFile.put("Bucket", ossInputBucket);
        try {
            inputFile.put("Object", URLEncoder.encode("wb-ceshi/487_93.mp4", "utf-8"));
        }catch (UnsupportedEncodingException e) {
            throw new RuntimeException("input URL encode failed");
        }
        return inputFile;
    }
    private JSONArray getOutputs()  throws ClientException{
        JSONArray outputs = new JSONArray();
        outputs.add(getOutput());
        return outputs;
    }
    private JSONObject getOutput()  throws ClientException{
        JSONObject output = new JSONObject();
        output.put("OutputObject", "wb-ceshi/20201117");
        output.put("TemplateId", "S00000001-100020");
        output.put("Encryption", getEncryptionConfigs());
        return output;
    }
    private JSONObject getEncryptionConfigs() throws ClientException  {
        GenerateDataKeyRequest generateDataKeyRequest=new GenerateDataKeyRequest();      
        generateDataKeyRequest.setKeyId("504d60ef-93f3-48e8-9d94-075aeb7d7003");  // 需换成KMS控制台的别名为alias/acs/mts的密钥。KMS控制台地址:https://kms.console.aliyun.com/cn-shanghai/key/list
        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;
    }
    private String submitJobs() throws ClientException {
        JSONObject inputFile = getInputFile();
        SubmitJobsRequest request = new SubmitJobsRequest();
        request.setInput(inputFile.toJSONString());
        request.setOutputLocation(location);
        request.setOutputBucket(ossOutputBucket);
        request.setOutputs(getOutputs().toJSONString());
        request.setPipelineId(pipelineId);
        SubmitJobsResponse reponse = this.client.getAcsResponse(request);
        System.out.println(JSONObject.toJSONString(reponse.getJobResultList()));
        return reponse.getJobResultList().get(0).getJob().getJobId();
    }
    public static void main(String[] args) throws ClientException {
        DataEncryptionBasicKMS demo = new DataEncryptionBasicKMS();
        String jobId= demo.submitJobs();
        System.out.println(jobId);
    }
}
            

解密示例代码(Base64方式)

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 java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;

public class Base64DecryptServer {
    /**
     * 说明:
     * 1、接收解密请求
     * 2、将明文密钥base64decode返回
     */
    public class Base64DecryptHandler implements HttpHandler {
        /**
         * 处理解密请求
         *
         * @param httpExchange
         * @throws IOException
         */
        public void handle(HttpExchange httpExchange) throws IOException {
            System.out.println("request in");
            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 serviceBootStrap() 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("hls decrypt server started");
    }
    public static void main(String[] args) throws IOException {
        Base64DecryptServer server = new Base64DecryptServer();
        server.serviceBootStrap();
    }
}

解密示例代码(KMS方式)

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 {  
        String accessKeyId = "***"; // RAM账号的AccessKey ID
        String accessKeySecret = "***"; // RAM账号Access Key Secret       
        String region = "cn-shanghai"; // KMS的区域,必须与视频区域对应
        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 {
            System.out.println("request in");
            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();
                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);
        }
        /**
         * 调用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();
        // 监听端口8888,能同时接受30个请求
        HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8888), 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();
    }
}