栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

阿里云视频点播 和HLS加密解密

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

阿里云视频点播 和HLS加密解密

背景:工作需要,领导让去研究阿里云视频点播,毕竟害怕付费视频被二次转发,导致视频的不安全。

HLS标准加密 - 视频点播 - 阿里云

前期准备:

1)开启视频点播控制台。

 2)设置转码模板组,因为看文档说加密有标准HLS加密和阿里私密加密和DRM加密(商业一点,贵贵),同时阿里私密加密有个不足就是IOS网页不能播放,所以这里使用HLS加密了,在这边也需要做点操作。

 具体某个画质里面,设置封装格式为HLS,高级参数那边设置私密加密。

 3)域名管理

只有添加分发加速的域名才能使用HLS加密,同时也要做HTTPS证书添加,不然也会报错。

 

 具体域名怎么配置可以看文档。

3)开启写代码了,做好依赖注入。

 

   
            com.aliyun
            aliyun-java-sdk-core
            4.5.1
        
        
            com.aliyun
            aliyun-java-sdk-vod
            2.15.11
        
        
            com.alibaba
            fastjson
            1.2.62
        
        
            com.aliyun
            aliyun-java-sdk-kms
            2.10.1
        

 4)获得上传凭证和重新获得上传凭证接口。这边采用前端上传视频,前端在一开始调用上传凭证是需要给fileName和title。重新获得凭证是在视频上传超时之后重新调用获得凭证,这是只需要一个videoID;

 
    public static baseVideo createUploadVideo(baseUpload baseUpload){
        DefaultAcsClient client = initVodClient();
        CreateUploadVideoRequest request = new CreateUploadVideoRequest();
        request.setTitle(baseUpload.getTitle());
        request.setFileName(baseUpload.getFileName());
        baseVideo baseVideo = new baseVideo();
        CreateUploadVideoResponse response = new CreateUploadVideoResponse();
        try {
            response=client.getAcsResponse(request);
            baseVideo.setUploadAddress(response.getUploadAddress());
            baseVideo.setVideoId(response.getVideoId());
            baseVideo.setUploadAuth(response.getUploadAuth());
        }catch (Exception e){
            baseVideo.setErrorMessage(e.getLocalizedMessage());
        }finally {
            baseVideo.setRequestId(response.getRequestId());
        }
        return baseVideo;
    }


    
    public static baseVideo refreshUploadVideo(String VideoId ){
        DefaultAcsClient client = initVodClient();
        RefreshUploadVideoRequest request = new RefreshUploadVideoRequest();
        request.setVideoId(VideoId);
        baseVideo baseVideo = new baseVideo();
        RefreshUploadVideoResponse response = new RefreshUploadVideoResponse();
        try {
            response=client.getAcsResponse(request);
            baseVideo.setUploadAddress(response.getUploadAddress());
            baseVideo.setVideoId(VideoId);
            baseVideo.setUploadAuth(response.getUploadAuth());
        }catch (Exception e){
            baseVideo.setErrorMessage(e.getLocalizedMessage());
        }finally {
            baseVideo.setRequestId(response.getRequestId());
        }
        return baseVideo;
    }

 5)上传成功后得到成功上传的回调信息,进行HLS加密。

先设置那些回调信息可以通过接口回调出来。

 

当然回调要是有人恶意多次请求该接口,会出现很多问题,所以需要进行一个回调鉴权。

HTTP回调鉴权 - 视频点播 - 阿里云

    
    public static Integer compareSignature(String url,String time,String key,String signature){
        Digester digester = new Digester(DigestAlgorithm.MD5);
        String digestHex = digester.digestHex(url+"|"+time+"|"+key);
        long localtime = System.currentTimeMillis() / 1000;
        long oldtime=Long.parseLong(time);
        if (localtime-oldtime>300000){
            return 2;
        }
        System.out.println(digestHex);
        System.out.println(signature);
        if (digestHex.equals(signature)){
            return 0;
        }else {
            return 1;
        }
    }

成功回调之后,就可以进行转码作业了

 
    public static baseCommit submitTranscodeJobs(String VideoId){

        try {
            DefaultAcsClient client = initVodClient();
            SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest();
            request.setVideoId(VideoId);
            request.setTemplateGroupId("44b01537a7bb10990e101f812d659478");
            JSonObject encryptConfig = buildEncryptConfig();
            //HLS标准加密配置(只有标准加密才需要传递)
            request.setEncryptConfig(encryptConfig.toJSonString());
            SubmitTranscodeJobsResponse acsResponse;
            acsResponse =  client.getAcsResponse(request);
            baseCommit baseCommit = new baseCommit();
            baseCommit.setCiphertext(encryptConfig.get("CipherText").toString());
            baseCommit.setMtsHlsUriToken(encryptConfig.getString("MtsHlsUriToken"));
            baseCommit.setJobId(acsResponse.getTranscodeJobs().get(0).getJobId());
            return baseCommit;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    
      public static JSonObject buildEncryptConfig() throws ClientException {
        DefaultAcsClient client = initVodClient();
        GenerateDataKeyResponse response = generateDataKey(client, serviceKey);
        JSonObject encryptConfig = new JSonObject();
           PlayToken playToken = new PlayToken();
           try {
//               String token = playToken.generateToken("sh12345678912345");
               encryptConfig.put("DecryptKeyUri", "http://IP:10089/decrypt?CipherText=" + response.getCiphertextBlob()+"&MtsHlsUriToken="+"HiZZg7kx0lUFWcByN9mGMG8V2SvprV07psRPFdM/f50=");
               encryptConfig.put("KeyServiceType", "KMS");
               encryptConfig.put("CipherText", response.getCiphertextBlob());
               encryptConfig.put("MtsHlsUriToken","HiZZg7kx0lUFWcByN9mGMG8V2SvprV07psRPFdM/f50=");
               return encryptConfig;
           } catch (Exception e) {
               e.printStackTrace();
           }
           return null;

    }


    
    public static GenerateDataKeyResponse generateDataKey(DefaultAcsClient client, String serviceKey) throws ClientException {
        GenerateDataKeyRequest request = new GenerateDataKeyRequest();
        request.setKeyId(serviceKey);
        request.setKeySpec("AES_128");
        return client.getAcsResponse(request);
    }

 看控制台的视频地址,要是有一个画面格式的mp4和别的进行转码成功的m3u8并带有标准加密,就意味着加密成功。

最后就是解密去看视频了。

 在加密转码接口之中有一个小细节。

 看了官网有个解密的服务。

直接可以用,但是推荐把token放入数据库,我还没做好。这个具体按照业务来嘛。

//加密服务
public class PlayToken {
    //非AES生成方式,无需以下参数
    private static String ENCRYPT_KEY = "1234561112345678";  //加密字符串,用户自行定义
    private static String INIT_VECTOR = "123456789123456g";  //长度为16的自定义字符串,不能有特殊字符。
    public static void main(String[] args) throws Exception {
        PlayToken playToken = new PlayToken();
        playToken.generateToken("sh12345678912349");
    }
    
    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位),用户可以自行更改。
        //生成token
        String token = encrypt(base, ENCRYPT_KEY);
        System.out.println(token);
        //保存token,用于解密时校验token的有效性,例如:过期时间、token的使用次数
        saveToken(token);
        return token;
    }
    
    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的有效性,业务方可自行实现
        Token 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);
    }
    
    public void saveToken(String token) {
        System.out.println(token);

        //TODO 存储Token
    }
    
    public Token getToken(String token) {
        //TODO 从DB 获取Token信息,用于校验有效性和合法性
        return null;
    }
    
    public boolean validateInfo(String... infos) {
        //TODO 校验信息的有效性,例如UID是否有效等
        return true;
    }
    
    public String encrypt(String value, String key) 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);
    }
    
    public String decrypt(String encrypted, String key) 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);
    }
    
    class Token {
        //Token的有效使用次数,分布式环境需要注意同步修改问题
        int useCount;
        //token内容
        String token;
    }}
//解密服务
public class HlsDecryptServer {
    private static DefaultAcsClient client;
    static {
        //KMS的区域,必须与视频对应区域
        String region = "";
        //访问KMS的授权AccessKey信息
        String accessKeyId="";
        String accessKeySecret="";
        client = new DefaultAcsClient(DefaultProfile.getProfile(region, accessKeyId, accessKeySecret));
    }
    
    public class HlsDecryptHandler implements HttpHandler {
        
        public void handle(HttpExchange httpExchange) throws IOException {
            String requestMethod = httpExchange.getRequestMethod();
            if ("GET".equalsIgnoreCase(requestMethod)) {
                //校验token的有效性
                String token = getMtsHlsUriToken(httpExchange);
                System.out.println("hh"+token);
                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);
        }
        
        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 boolean validateToken(String token) {
            if (null == token || "".equals(token)) {
                return false;
            }
            //TODO 业务方实现令牌有效性校验
            return true;
        }
        
        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;
            }
        }
        
        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;
            }
        }
    }
    
    public void serviceBootStrap() throws IOException {
        HttpServerProvider provider = HttpServerProvider.provider();
        //监听端口可以自定义,能同时接受最多30个请求
        HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(10089), 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();
    }}

尤其让我困惑好久的是这边解密的端口号是和上述uri一样的端口号,我这个研究了一天,我好像一个憨批。

最后把解密服务当做一个bean,当系统运行的时候,服务也就开着了。

 大致说下感受:加密还是蛮简单的,解密的话就是我后端从阿里云得到视频的地址(加密m3u8格式),播放器知道这个是加密视频,就会通过解密接口来进行解密,最后就是解密之后的播放地址。其实也还行,就是文档有点杂,要东拼西凑的看东西。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/269979.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号