当前位置:   article > 正文

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

hls链接解密

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

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

前期准备:

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

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

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

 3)域名管理

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

 

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

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

 

  1. <dependency>
  2. <groupId>com.aliyun</groupId>
  3. <artifactId>aliyun-java-sdk-core</artifactId>
  4. <version>4.5.1</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.aliyun</groupId>
  8. <artifactId>aliyun-java-sdk-vod</artifactId>
  9. <version>2.15.11</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>com.alibaba</groupId>
  13. <artifactId>fastjson</artifactId>
  14. <version>1.2.62</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>com.aliyun</groupId>
  18. <artifactId>aliyun-java-sdk-kms</artifactId>
  19. <version>2.10.1</version>
  20. </dependency>

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

  1. /**
  2. * 获取视频上传地址和凭证
  3. * @return CreateUploadVideoResponse 获取视频上传地址和凭证响应数据
  4. */
  5. public static BaseVideo createUploadVideo(BaseUpload baseUpload){
  6. DefaultAcsClient client = initVodClient();
  7. CreateUploadVideoRequest request = new CreateUploadVideoRequest();
  8. request.setTitle(baseUpload.getTitle());
  9. request.setFileName(baseUpload.getFileName());
  10. BaseVideo baseVideo = new BaseVideo();
  11. CreateUploadVideoResponse response = new CreateUploadVideoResponse();
  12. try {
  13. response=client.getAcsResponse(request);
  14. baseVideo.setUploadAddress(response.getUploadAddress());
  15. baseVideo.setVideoId(response.getVideoId());
  16. baseVideo.setUploadAuth(response.getUploadAuth());
  17. }catch (Exception e){
  18. baseVideo.setErrorMessage(e.getLocalizedMessage());
  19. }finally {
  20. baseVideo.setRequestId(response.getRequestId());
  21. }
  22. return baseVideo;
  23. }
  24. /**
  25. * 刷新视频上传凭证
  26. * @return RefreshUploadVideoResponse 刷新视频上传凭证响应数据
  27. */
  28. public static BaseVideo refreshUploadVideo(String VideoId ){
  29. DefaultAcsClient client = initVodClient();
  30. RefreshUploadVideoRequest request = new RefreshUploadVideoRequest();
  31. request.setVideoId(VideoId);
  32. BaseVideo baseVideo = new BaseVideo();
  33. RefreshUploadVideoResponse response = new RefreshUploadVideoResponse();
  34. try {
  35. response=client.getAcsResponse(request);
  36. baseVideo.setUploadAddress(response.getUploadAddress());
  37. baseVideo.setVideoId(VideoId);
  38. baseVideo.setUploadAuth(response.getUploadAuth());
  39. }catch (Exception e){
  40. baseVideo.setErrorMessage(e.getLocalizedMessage());
  41. }finally {
  42. baseVideo.setRequestId(response.getRequestId());
  43. }
  44. return baseVideo;
  45. }

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

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

 

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

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

  1. /**
  2. * 回调比较是否合法
  3. */
  4. public static Integer compareSignature(String url,String time,String key,String signature){
  5. Digester digester = new Digester(DigestAlgorithm.MD5);
  6. String digestHex = digester.digestHex(url+"|"+time+"|"+key);
  7. long localtime = System.currentTimeMillis() / 1000;
  8. long oldtime=Long.parseLong(time);
  9. if (localtime-oldtime>300000){
  10. return 2;
  11. }
  12. System.out.println(digestHex);
  13. System.out.println(signature);
  14. if (digestHex.equals(signature)){
  15. return 0;
  16. }else {
  17. return 1;
  18. }
  19. }

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

  1. /**
  2. * 提交媒体处理作业
  3. */
  4. public static BaseCommit submitTranscodeJobs(String VideoId){
  5. try {
  6. DefaultAcsClient client = initVodClient();
  7. SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest();
  8. request.setVideoId(VideoId);
  9. request.setTemplateGroupId("44b01537a7bb10990e101f812d659478");
  10. JSONObject encryptConfig = buildEncryptConfig();
  11. //HLS标准加密配置(只有标准加密才需要传递)
  12. request.setEncryptConfig(encryptConfig.toJSONString());
  13. SubmitTranscodeJobsResponse acsResponse;
  14. acsResponse = client.getAcsResponse(request);
  15. BaseCommit baseCommit = new BaseCommit();
  16. baseCommit.setCiphertext(encryptConfig.get("CipherText").toString());
  17. baseCommit.setMtsHlsUriToken(encryptConfig.getString("MtsHlsUriToken"));
  18. baseCommit.setJobId(acsResponse.getTranscodeJobs().get(0).getJobId());
  19. return baseCommit;
  20. }catch (Exception e){
  21. e.printStackTrace();
  22. return null;
  23. }
  24. }
  25. /**
  26. * 构建HLS标准加密的配置信息
  27. * @return
  28. * @throws ClientException
  29. */
  30. public static JSONObject buildEncryptConfig() throws ClientException {
  31. DefaultAcsClient client = initVodClient();
  32. GenerateDataKeyResponse response = generateDataKey(client, serviceKey);
  33. JSONObject encryptConfig = new JSONObject();
  34. PlayToken playToken = new PlayToken();
  35. try {
  36. // String token = playToken.generateToken("sh12345678912345");
  37. encryptConfig.put("DecryptKeyUri", "http://IP:10089/decrypt?CipherText=" + response.getCiphertextBlob()+"&MtsHlsUriToken="+"HiZZg7kx0lUFWcByN9mGMG8V2SvprV07psRPFdM/f50=");
  38. encryptConfig.put("KeyServiceType", "KMS");
  39. encryptConfig.put("CipherText", response.getCiphertextBlob());
  40. encryptConfig.put("MtsHlsUriToken","HiZZg7kx0lUFWcByN9mGMG8V2SvprV07psRPFdM/f50=");
  41. return encryptConfig;
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. }
  45. return null;
  46. }
  47. /**
  48. * 生成加密需要的密钥,response中包含密文密钥和明文密钥,用户只需要将密文密钥传递给点播即可
  49. * 注意:KeySpec 必须传递AES_128,且不能设置NumberOfBytes
  50. * @param client KMS-SDK客户端
  51. * @param serviceKey 点播提供生成密钥的service key,在用户的密钥管理服务中可看到描述为vod的加密key
  52. * @return
  53. * @throws ClientException
  54. */
  55. public static GenerateDataKeyResponse generateDataKey(DefaultAcsClient client, String serviceKey) throws ClientException {
  56. GenerateDataKeyRequest request = new GenerateDataKeyRequest();
  57. request.setKeyId(serviceKey);
  58. request.setKeySpec("AES_128");
  59. return client.getAcsResponse(request);
  60. }

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

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

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

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

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

  1. //加密服务
  2. public class PlayToken {
  3. //非AES生成方式,无需以下参数
  4. private static String ENCRYPT_KEY = "1234561112345678"; //加密字符串,用户自行定义
  5. private static String INIT_VECTOR = "123456789123456g"; //长度为16的自定义字符串,不能有特殊字符。
  6. public static void main(String[] args) throws Exception {
  7. PlayToken playToken = new PlayToken();
  8. playToken.generateToken("sh12345678912349");
  9. }
  10. /**
  11. * 根据传递的参数生成令牌
  12. * 说明:
  13. * 1、参数可以是业务方的用户ID、播放终端类型等信息
  14. * 2、调用令牌接口时生成令牌Token
  15. * @param args
  16. * @return
  17. */
  18. public String generateToken(String... args) throws Exception {
  19. if (null == args || args.length <= 0) {
  20. return null;
  21. }
  22. String base = StringUtils.join(Arrays.asList(args), "_");
  23. //设置30S后,该token过期,过期时间可以自行调整
  24. long expire = System.currentTimeMillis() + 30000L;
  25. base += "_" + expire; //base最终的字符串长度和时间戳一起要保证是16位(其中时间戳13位),用户可以自行更改。
  26. //生成token
  27. String token = encrypt(base, ENCRYPT_KEY);
  28. System.out.println(token);
  29. //保存token,用于解密时校验token的有效性,例如:过期时间、token的使用次数
  30. saveToken(token);
  31. return token;
  32. }
  33. /**
  34. * 验证token的有效性
  35. * 说明:
  36. * 1、解密接口在返回播放密钥前,需要先校验Token的合法性和有效性
  37. * 2、强烈建议同时校验Token的过期时间以及Token的有效使用次数
  38. * @param token
  39. * @return
  40. * @throws Exception
  41. */
  42. public boolean validateToken(String token) throws Exception {
  43. if (null == token || "".equals(token)) {
  44. return false;
  45. }
  46. String base = decrypt(token, ENCRYPT_KEY);
  47. //先校验token的有效时间
  48. Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1));
  49. if (System.currentTimeMillis() > expireTime) {
  50. return false;
  51. }
  52. //从DB获取token信息,判断token的有效性,业务方可自行实现
  53. Token dbToken = getToken(token);
  54. //判断是否已经使用过该token
  55. if (dbToken == null || dbToken.useCount > 0) {
  56. return false;
  57. }
  58. //获取到业务属性信息,用于校验
  59. String businessInfo = base.substring(0, base.lastIndexOf("_"));
  60. String[] items = businessInfo.split("_");
  61. //校验业务信息的合法性,业务方实现
  62. return validateInfo(items);
  63. }
  64. /**
  65. * 保存Token到DB
  66. * 业务方自行实现
  67. *
  68. * @param token
  69. */
  70. public void saveToken(String token) {
  71. System.out.println(token);
  72. //TODO 存储Token
  73. }
  74. /**
  75. * 查询Token
  76. * 业务方自行实现
  77. *
  78. * @param token
  79. */
  80. public Token getToken(String token) {
  81. //TODO 从DB 获取Token信息,用于校验有效性和合法性
  82. return null;
  83. }
  84. /**
  85. * 校验业务信息的有效性,业务方可自行实现
  86. *
  87. * @param infos
  88. * @return
  89. */
  90. public boolean validateInfo(String... infos) {
  91. //TODO 校验信息的有效性,例如UID是否有效等
  92. return true;
  93. }
  94. /**
  95. * AES加密生成Token
  96. *
  97. * @param key
  98. * @param value
  99. * @return
  100. * @throws Exception
  101. */
  102. public String encrypt(String value, String key) throws Exception {
  103. IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
  104. SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
  105. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
  106. cipher.init(Cipher.ENCRYPT_MODE, skeySpec, e);
  107. byte[] encrypted = cipher.doFinal(value.getBytes());
  108. return Base64.encodeBase64String(encrypted);
  109. }
  110. /**
  111. * AES解密token
  112. *
  113. * @param key
  114. * @param encrypted
  115. * @return
  116. * @throws Exception
  117. */
  118. public String decrypt(String encrypted, String key) throws Exception {
  119. IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
  120. SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
  121. Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
  122. cipher.init(Cipher.DECRYPT_MODE, skeySpec, e);
  123. byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
  124. return new String(original);
  125. }
  126. /**
  127. * Token信息,业务方可提供更多信息,这里仅仅给出示例
  128. */
  129. class Token {
  130. //Token的有效使用次数,分布式环境需要注意同步修改问题
  131. int useCount;
  132. //token内容
  133. String token;
  134. }}
  1. //解密服务
  2. public class HlsDecryptServer {
  3. private static DefaultAcsClient client;
  4. static {
  5. //KMS的区域,必须与视频对应区域
  6. String region = "";
  7. //访问KMS的授权AccessKey信息
  8. String accessKeyId="";
  9. String accessKeySecret="";
  10. client = new DefaultAcsClient(DefaultProfile.getProfile(region, accessKeyId, accessKeySecret));
  11. }
  12. /**
  13. * 说明:
  14. * 1、接收解密请求,获取密文密钥和令牌Token
  15. * 2、调用KMS decrypt接口获取明文密钥
  16. * 3、将明文密钥base64decode返回
  17. */
  18. public class HlsDecryptHandler implements HttpHandler {
  19. /**
  20. * 处理解密请求
  21. * @param httpExchange
  22. * @throws IOException
  23. */
  24. public void handle(HttpExchange httpExchange) throws IOException {
  25. String requestMethod = httpExchange.getRequestMethod();
  26. if ("GET".equalsIgnoreCase(requestMethod)) {
  27. //校验token的有效性
  28. String token = getMtsHlsUriToken(httpExchange);
  29. System.out.println("hh"+token);
  30. boolean validRe = validateToken(token);
  31. if (!validRe) {
  32. return;
  33. }
  34. //从URL中取得密文密钥
  35. String ciphertext = getCiphertext(httpExchange);
  36. if (null == ciphertext)
  37. return;
  38. //从KMS中解密出来,并Base64 decode
  39. byte[] key = decrypt(ciphertext);
  40. //设置header
  41. setHeader(httpExchange, key);
  42. //返回base64decode之后的密钥
  43. OutputStream responseBody = httpExchange.getResponseBody();
  44. responseBody.write(key);
  45. responseBody.close();
  46. }
  47. }
  48. private void setHeader(HttpExchange httpExchange, byte[] key) throws IOException {
  49. Headers responseHeaders = httpExchange.getResponseHeaders();
  50. responseHeaders.set("Access-Control-Allow-Origin", "*");
  51. httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, key.length);
  52. }
  53. /**
  54. * 调用KMS decrypt接口解密,并将明文base64decode
  55. * @param ciphertext
  56. * @return
  57. */
  58. private byte[] decrypt(String ciphertext) {
  59. DecryptRequest request = new DecryptRequest();
  60. request.setCiphertextBlob(ciphertext);
  61. request.setProtocol(ProtocolType.HTTPS);
  62. try {
  63. DecryptResponse response = client.getAcsResponse(request);
  64. String plaintext = response.getPlaintext();
  65. //注意:需要base64 decode
  66. return Base64.decodeBase64(plaintext);
  67. } catch (ClientException e) {
  68. e.printStackTrace();
  69. return null;
  70. }
  71. }
  72. /**
  73. * 校验令牌有效性
  74. * @param token
  75. * @return
  76. */
  77. private boolean validateToken(String token) {
  78. if (null == token || "".equals(token)) {
  79. return false;
  80. }
  81. //TODO 业务方实现令牌有效性校验
  82. return true;
  83. }
  84. /**
  85. * 从URL中获取密文密钥参数
  86. * @param httpExchange
  87. * @return
  88. */
  89. private String getCiphertext(HttpExchange httpExchange) {
  90. URI uri = httpExchange.getRequestURI();
  91. String queryString = uri.getQuery();
  92. String pattern = "CipherText=(\\w*)";
  93. Pattern r = Pattern.compile(pattern);
  94. Matcher m = r.matcher(queryString);
  95. if (m.find())
  96. return m.group(1);
  97. else {
  98. System.out.println("Not Found CipherText Param");
  99. return null;
  100. }
  101. }
  102. /**
  103. * 获取Token参数
  104. *
  105. * @param httpExchange
  106. * @return
  107. */
  108. private String getMtsHlsUriToken(HttpExchange httpExchange) {
  109. URI uri = httpExchange.getRequestURI();
  110. String queryString = uri.getQuery();
  111. String pattern = "MtsHlsUriToken=(\\w*)";
  112. Pattern r = Pattern.compile(pattern);
  113. Matcher m = r.matcher(queryString);
  114. if (m.find())
  115. return m.group(1);
  116. else {
  117. System.out.println("Not Found MtsHlsUriToken Param");
  118. return null;
  119. }
  120. }
  121. }
  122. /**
  123. * 服务启动
  124. *
  125. * @throws IOException
  126. */
  127. public void serviceBootStrap() throws IOException {
  128. HttpServerProvider provider = HttpServerProvider.provider();
  129. //监听端口可以自定义,能同时接受最多30个请求
  130. HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(10089), 30);
  131. httpserver.createContext("/", new HlsDecryptHandler());
  132. httpserver.start();
  133. System.out.println("hls decrypt server started");
  134. }
  135. public static void main(String[] args) throws IOException {
  136. HlsDecryptServer server = new HlsDecryptServer();
  137. server.serviceBootStrap();
  138. }}

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

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

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

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号