业务场景
当前阿里云视频点播服务有两种加密方式:1、阿里云私有加密;(目前移动端H5兼容性不好,H5仅支持Android的Chrome浏览器)2、HLS标准加密;(支持H5)
移动端以及PC兼容情况:
解决方案
1、阿里云私有加密
1)加密文件生成,转码模板处勾选“HLS加密”即可。
生成之后的加密文件是下面的样式:
同样也可以通过curl去查看您的视频是否是阿里云私有加密,curl出来的结果是URI=xxx则为阿里云私有加密视频
2)阿里云私有加密视频播放,私有加密的播放需要满足两个条件:1、使用阿里云提供的播放器;(当前有web、Android以及iOS)2、使用videoID+STS或者videoID+playauth方式播放。
web demo:https://player.alicdn.com/aliplayer/setting/setting.html
android以及iOS demo:https://help.aliyun.com/document_detail/51992.html
2、HLS标准加密
1)生成HLS标准加密视频,目前生成HLS标准加密的视频只能通过API接口或者SDK进行处理。(也就是只能代码生成,无法直接使用控制台)
逻辑主要是:
1、创建加密转码模板
2、RAM授权
使用RAM服务给视频点播授权访问业务方秘钥管理服务(KMS)的权限
3、创建Service Key(KMS控制台创建即可)
4、提交加密转码:
1)通过接口:GenerateDataKey生成明文秘钥和密文秘钥之后,再提交转码作业;(EncryptConfig里写HLS标准加密信息)
GenerateDataKey:https://help.aliyun.com/document_detail/28948.html
提交转码作业接口:https://help.aliyun.com/document_detail/68570.html
2)直接使用提交转码作业是SDK demo(推荐使用)
以Java为例子:https://help.aliyun.com/document_detail/98672.html
只需要确认AK、SK、Service Key以及DecryptKeyUri正确即可
最终生成的HLS标准加密的视频:
通过curl进行验证,得到的URI是http://解密服务地址/xxx?Ciphertext=xxx 这样的连接,则表示加密成功了。
5、解密HLS标准加密视频
1、谈到解密,大家会比较关心一个令牌HlsMtsToken,这个其实是一个附件的选项。(可有可无)举个例子:
我生成的加密地址是:https://vod.xxxx.cn/8e1f0d9295cf41989a837f4aaab7a813/87ba78922d59fe6cba843e48b5ccb659-hd-encrypt-stream.m3u8
如果我的服务器需要需要有个校验值的话,就可以写成:https://vod.xxxx.cn/8e1f0d9295cf41989a837f4aaab7a813/87ba78922d59fe6cba843e48b5ccb659-hd-encrypt-stream.m3u8?MtsHlsUriToken=xxxx
注意:MtsHlsUriToken=xxxx这个是解密服务器自己判断进行校验的,但是需要提交工单给阿里云的工程师,配置,请求解密URI的时候,带上这个参数。
2、解密服务器搭建
以Java为例子,如果是其他语言的话,按照下述的步骤写就好。
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 = "<您使用的AccessKeyId>";
String accessKeySecret = "<您使用的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();
}
}