知识付费——移动端音视频加密、防盗播实现方案
近几年移动端知识付费App,越来越多越来越火爆。例如:网易公开课、网易云课堂、樊登读书、逻辑思维、i春秋,甚至于知乎App都上架付费课程了。
移动端付费课程,基本是以视频+音频
形式呈现给大家的。那么对于这些付费课程:如何在保证用户体验的前提下,防止媒体资源被盗播,维护内容生产者的利益,就成为一个急需解决的问题
。
注意:保证用户体验是前提。否则再好的课,用户体验垃圾,也卖不出去。
如今市面上,移动端加密、防盗播的方式很多。这里只是讨论一种:我认为的用户体验较好,技术实现成熟,又有效防盗播的方式
。
注意:防止盗播,并不能100%杜绝盗播。只能不断增加App的破解成本,完全无法破解的App是不存在的。所以,想100%防止盗播也是不可能实现的。
一、实现方案
这里采用的方案是:客户端播放AES-128加密的m3u8媒体资源
为什么是m3u8 ?
- m3u8采用AES-128对称加密算法加密,技术成熟稳定
- 前边说了,保证用户体验为前提
音视频播放过程中,用户进入播放页后,音视频的秒开率
(1秒内成功加载的播放数/播放总数)是影响用户体验的重要指标;m3u8媒体资源是一个文本文件,其由一个个ts视频片段的播放地址构成,选择合适的ts切片大小,能有效提高音视频的秒开率,保证用户的观看体验。
用户体验的大前提满足了,那如何实现呢?
- 客户端播放的视频源为
AES-128对称加密的 m3u8
媒体资源;
播放地址最终组织形式如下:
https://domain/course101.m3u8?playKey=i_am_decrypting_key101
- 播放地址
https://domain/course101.m3u8
与playKey
需从服务端获取;
加密m3u8的解密关键就在于playKey
,因此,防止playKey被破解是防盗播的一个关键点
- 将最终播放地址交于播放器播放
二、实现举例
对于m3u8媒体资源,无论是 视频切片加密
、ts视频片段解密播放
在技术实现上已经非常成熟,不存在技术壁垒,实现已经不是问题。
那么摆在移动端最主要的问题就是:防止媒体资源被盗播,维护内容生产者的利益
注:虽然技术实现已经不是问题,但这里还是会把 移动端技术实现的细节
和 防破解(防盗播)
的实现细节进行呈现。
技术实现上,大概分为以下几个步骤:
- 获取媒体资源播放地址
- 获取媒体资源的解密token (playKey)
- 拼接最终播放地址,播放m3u8媒体资源
下面我们从移动端请求课程内容列表,到成功播放的全过程来举一个例子:
1、获取ID为101课程的内容列表
首先通过课程ID 101向服务端发起请求,获取该课程的内容列表。请求方式和返回结果如下:
a、发起请求:
https://domain/xxx/getCourseList.do?courseId=101
为了简单易懂,这里简单模拟一个get请求:
- 请求参数
courseId
为101
; - 请求地址为
https://domain/xxx/getCourseList.do
; - 获取id为101的课程,其对应的内容列表。
b、返回结果:
课程101的课程内容列表如下:
[
{
"mediaId": "101", //媒体资源id
"mediaUrl": "n3GuNo5Rc44anmLGrRU8Rne/JU9cHzc1vXZWiYEwcD0=", // 媒体资源的m3u8播放地址
"encryptId": "LLVahEH+HjZEOJk6RfJtww==" // 获取媒体资源playKey的encryptId
},
{
"mediaId": "102",
"mediaUrl": "n3GuNo5Rc44anmLGrRU8RlKQGcM3X5R3oVqpuCrTpDk=",
"encryptId": "Ez3wHHWx5ddq+D2miV2dFg=="
},
{
"mediaId": "103",
"mediaUrl": "n3GuNo5Rc44anmLGrRU8RqLaIqPDWyZQ5VotuUSyNI8=",
"encryptId": "R5a31ZJIy9Z5+tNPplxHLQ=="
}
]
正如注释中写的:
-
mediaId
是媒体资源id
; -
mediaUrl
是媒体资源的m3u8播放地址
;
可能会疑问: n3GuNo5Rc44anmLGrRU8Rne/JU9cHzc1vXZWiYEwcD0= 是什么鬼,为什么不像播放地址?
这里正是防止媒体资源被盗播,维护内容生产者的利益的 第二步
这里将资源地址https://domain/course101.m3u8
进行了一个简单的AES加密
对于其资源地址的加解密,会在下文进行详细说明。
-
encryptId
则是用于获取媒体资源的解密playKey
。
encryptId的具体作用,会在下文中详细说明
注:这里服务器要做一个用户权限的校验:未购买用户不返回其 mediaUrl 和 encryptId
c、未购买用户的返回结果:
未购买用户不返回其 mediaUrl 和 encryptId
正是预防移动端被不良用心的人员,恶意采用Http抓包进行破解。是防止媒体资源被盗播,维护内容生产者的利益的 第一步
,也是很重要的一步。 因为:
- 移动端无论如何防破解,总不能100%保证无法破解;
- 想要获取播放地址,必须购买课程,对于用心不了的人员,增加了其破解成本;
未购买用户,通过课程ID 101向服务端发起请求,返回数据如下:
[
{
"mediaId": "101",
"mediaUrl": "",
"encryptId": ""
},
{
"mediaId": "102",
"mediaUrl": "",
"encryptId": ""
},
{
"mediaId": "103",
"mediaUrl": "",
"encryptId": ""
}
]
注:未购买用户,请求某课程列表接口时,不要返回 媒体资源的m3u8播放地址
和 获取媒体资源playKey的encryptId
。这很重要
d、mediaUrl与encryptId解密:
为防止移动端App被Http抓包,mediaUrl
与 encryptId
都不是明文传输的。因此移动端拿到课程的内容列表后,首先需要对mediaUrl
与 encryptId
进行解密。
关于解密:
- 对称加密算法,大家可以根据实际需求,自行进行选择;
- 解密相关代码,
建议由C语言实现,打成SO添加到移动客户端中
;
原因是增加移动端破解难道,提高破解成本
。
这里只是采用了简单的AES对称加密:
完成解密后的数据格式如下:
[
{
"mediaId": "101", //媒体资源id
"mediaUrl": "https://domain/course101.m3u8", // 媒体资源的m3u8播放地址
"encryptId": "qazwsx101" // 获取媒体资源playKey的encryptId
},
{
"mediaId": "102",
"mediaUrl": "https://domain/course102.m3u8",
"encryptId": "qazwsx102"
},
{
"mediaId": "103",
"mediaUrl": "https://domain/course103.m3u8",
"encryptId": "qazwsx103"
}
]
2、获取 playKey
在第1步中,已成功获取到媒体资源的播放地址 https://domain/course101.m3u8
与encryptId
。为了播放媒体资源,下面来获取到加密m3u8视频的解密playKey
这里 playKey
仍然需要从服务端进行获取,获取方式如下:
模拟请求
发起请求:
https://domain/xxx/getMediaToken.do?encryptId=C0AFC0A81&mediaId=101
返回结果:
{
"playKey": "I_am_playkey_001"
}
这里仍然模拟一个简单get请求:
- 请求参数
encryptId
为qazwsx101
; - 请求参数
mediaId
为101
; - 请求地址为
https://domain/xxx/getMediaToken.do
;
注:playKey是加密m3u8解密的关键,是防止媒体资源被破解的重中之重。
关于 playKey 加密
这里是防止媒体资源被盗播,维护内容生产者的利益的 第三步和第四步
- 1、
playKey
服务端需加密传输
; - 2、请求
getMediaToken.do
接口时,服务端需对该用户的购买状态进行验证,未购用户不返回其对应的playKey; - 3、移动端获取到playKey后,在SO(C层代码达成的)中对playKey进行解密;
- 4、解密后的playKey存在一定的过期时间;或者使用次数限制(建议服务端限定只能使用一次)
加密算法要与mediaUrl 和encryptId 的解密算法区分开,增加破解成本
3、拼接播放地址
经过以上步骤,购买用户成功获取到 播放地址 https://domain/xxx/getMediaToken.do
和解密key I_am_playkey_001
。
将mediaUrl 与playKey 拼接起来,可以交给播放器播放了:
https://domain/course101.m3u8?playKey=i_am_decrypting_key101
到此,移动端的工作完成。
OK. 移动端完事大吉
。
到这里播放器与服务端的交互刚刚开始。
这里有必要先介绍一下,加密m3u8视频的文件格式:
加密m3u8视频的文件格式
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:19
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="http://domain//hls.key"
#EXT-CUSTOM-YUNXIN:ntsversion=0,ntsprivatedata=d68edb4f4ee3c64233b1ece785758291727d053d
#EXTINF:11.960000,
http://domain/encrypt0.ts
#EXTINF:9.200000,
http://domain/encrypt1.ts
#EXTINF:10.000000,
http://domain/encrypt2.ts
#EXT-X-ENDLIST
以上为加密m3u8视频的文件格式:
- 播放器在播放前,会用该地址
http://domain//hls.key
向业务服务器
发起请求,请求加密m3u8视频的解密秘钥
;
解密秘钥如下图所示:
加密m3u8视频的解密秘钥
并非playKey=i_am_decrypting_key101
,而是一个16字节的文件。
m3u8的解密秘钥实际是一个16字节的文件。
这里肯定会有很多疑问,别着急,我们继续前边的话题播放器
与服务端
交互。
播放器 与 服务端 交互
- 当
播放器
向服务端
发起https://domain/course101.m3u8?playKey=i_am_decrypting_key101
请求后; -
服务端
处理相对复杂
a、首先校验playKey=i_am_decrypting_key101
是否有效;
b、若有效则将加密m3u8视频中的http://domain//hls.key
更换为http://domain//hls.key?playKey=i_am_decrypting_key101
;
c、下发m3u8文件给播放器;
-
播放器
收到服务端
下发的加密m3u8
文件后:
a、播放器
读取加密m3u8
文件,获取解密秘钥
的请求地址http://domain//hls.key?playKey=i_am_decrypting_key101
;
b、播放器
向服务端
发起http://domain//hls.key?playKey=i_am_decrypting_key101
请求,请求解密秘钥; -
服务端
收到播放器
的请求后,需对playKey=i_am_decrypting_key101
进行校验,查看是否过期或者被使用过,若存在异常则不下发秘钥;若正常,则下发秘钥 -
播放器
收到秘钥文件后,正常播放;
OK. 这次是真的 完事大吉
。
4、移动端加固
如果仅仅做到上一步就结束了,移动端被破解的概率还是很高。因为SO任然可以被反编译破解,而且SO的反编译技术也已经相当成熟。
所以,无论在SO中采用了怎样复杂的加密算法,SO被反编译后,还是有被破解的可能
。
因此,以上步骤都完成后,我们仍然要对我们最终生成的APP进行一次加密。Android端推荐采用APP加固处理
。
5、SO动态下发,定期更新加密算法
以上步骤都做完,如果APP仍然被破解了。我们可以再加一层防破解处理:动态下发SO,定期更新加密算法
。
这样就算线上APP被破解了,也可以在不发版的情况下从容应对,更新一下加密算法就可以了。
三、总结一下
这里来简单总结一下,上边介绍的实现步骤 :
- a、移动端拿 courseId 向服务端发起Http请求,
获取课程的内容列表
; - b、服务端对用户进行权限验证,
未购买用户不返回其对应的播放地址
; - c、移动端
在SO层中对 mediaUrl与 encryptId 进行解密
; - d、移动端 用解密后的encryptId,
向服务端请求对应媒体资源的 playKey
- e、服务端对用户进行权限验证,
未购买用户不返回其对应的 playKey
; - f、移动端
在SO层中对 playKey 进行解密
;
注意,一定要与第三步的解密算法区分开
- g、
拼接mediaUrl 与 playKey
,交给播放器播放;
到此,移动端工作完成。
- h、
播放器
向服务端
发起下发m3u8请求 - i、
服务端
校验playKey;并动态替换m3u8中的URI字段 - j、
播放器
从m3u8文件中取出URI字段后,向服务端请求解密秘钥; - k、
服务端
校验playKey;并返回解密秘钥文件; - l、
播放器
播放
到此,加密m3u8 终于播放出来了。
- m、移动端App加固处理;
- n、为增强破解难度,这里解密
SO可由动态下发,定期更新加密算法
; - m、如果仍然担心App被破解,服务端下发的
解密秘钥文件
可以设置为非明文状态;客户端拿到解密秘钥后,以本地代理的方式设置给播放器进行播放。
注:这里有三个关键步骤:1、服务端对用户进行权限验证,未购买用户不返回其对应的播放地址;2、移动端 在SO层中对 playKey 进行解密;3、移动端App加固处理。
这三个关键步骤缺一不可,缺少任何一个移动端的破解难度和成本都将大大降低。
- 未购买用户不返回其对应的播放地址
可以极大的降低付费课程被大面积破解的情况出现。因为购买课程亦需要成本。破解的课程越多,那需要购买的课程就越多,破解成本也就越高。
- 在SO层中对 playKey 进行解密
对于加密m3u8媒体资源,破解的关键就在于playKey,因此playKey的加密算法应足够的复杂
- 移动端App加固处理
移动端App加固,是移动端代码反编译非常重要的一环。如果没有这一环在SO层中对 playKey 进行解密
的作用不是很大,因为无论SO中加密算法如何复杂,只要SO被翻遍后,再复杂的加密算法也没有意义了。
以上为全部处理流程
如果以上步骤都做了,付费音视频被盗播的可能性应该不大;但仍然存在Http抓包的可能性:
- http抓包时,会抓到
https://domain/course101.m3u8?playKey=i_am_decrypting_key101
请求
由于playKey只有一次使用有效,或者几个小时的超时时间。因此该内容无法被大规模传播;
- http抓包时,会抓到播放器的秘钥下发请求
http://domain//hls.key?playKey=i_am_decrypting_key101
;
由于服务端存在playKey有效性的校验,下次使用失效,因此也无法大规模传播;
如果对服务端明文秘钥文件下发存在疑虑,可以做以上流程图的“防盗播9”;
-
防盗播9
加上APP加固
,在实现上应该是比较安全了
如果这样仍被破解,那我也只能表示佩服 佩服