用火车头测试采集美拍的数据时无意中发现美拍的视频地址是一段加了混淆字符串的base64代码。如下图
于是好奇之下研究了下解密算法。具体过程省略800字。发现美拍的视频解密是通过js完成,于是找到了具体的解密代码,如下:
;(function(a) {
var b = "substring",
c = "split",
d = "replace",
e = "substr",
f = {
getHex: function(a) {
return {
str: a[b](4),
hex: a[b](0, 4)[c]("").reverse().join("")
}
},
getDec: function(a) {
var d = parseInt(a, 16).toString();
return {
pre: d[b](0, 2)[c](""),
tail: d[b](2)[c]("")
}
},
substr: function(a, c) {
var f = a[b](0, c[0]),
g = a[e](c[0], c[1]);
return f + a[b](c[0])[d](g, "")
},
getPos: function(a, b) {
return b[0] = a.length - b[0] - b[1],
b
},
decode: function(a) {
var b = this.getHex(a),
c = this.getDec(b.hex),
d = this[e](b.str, c.pre);
return window.atob(this[e](d, this.getPos(d, c.tail)))
}
};
a.decodeMp4 = f,
window.MP = a
} (window.MP || {}))
通过解密代码可以发现视频地址字符串是base64加密的,只不过在其中插入了一些混淆字符。只要清除混淆字符即可通过base64解密得到视频地址。
js怎样对字符进行base64加密、解密?如下:
window.atob(str);//解密
window.btoa(str);//加密
//但后来发现这样是不兼容中文的,于是有了下面的兼容中文的方法
decodeURIComponent(escape(window.atob(d)));//解密
window.btoa(unescape(encodeURIComponent(str)));//加密
由于ie兼容问题,所以另外找了个封装好的:
base64={
atob:function(src){//解密
//用一个数组来存放解码后的字符。
var str=new Array();
var ch1, ch2, ch3, ch4;
var pos=0;
//过滤非法字符,并去掉'='。
src=src.replace(/[^A-Za-z0-9\+\/]/g, '');
//decode the source string in partition of per four characters.
while(pos+4<=src.length){
ch1=this.deKey[src.charCodeAt(pos++)];
ch2=this.deKey[src.charCodeAt(pos++)];
ch3=this.deKey[src.charCodeAt(pos++)];
ch4=this.deKey[src.charCodeAt(pos++)];
str.push(String.fromCharCode(
(ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2), (ch3<<6&0xff)+ch4));
}
//给剩下的字符进行解码。
if(pos+1<src.length){
ch1=this.deKey[src.charCodeAt(pos++)];
ch2=this.deKey[src.charCodeAt(pos++)];
if(pos<src.length){
ch3=this.deKey[src.charCodeAt(pos)];
str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2)));
}else{
str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4)));
}
}
//组合各解码后的字符,连成一个字符串。
return str.join('');
},
btoa:function(src){//加密
//用一个数组来存放编码后的字符,效率比用字符串相加高很多。
var str=new Array();
var ch1, ch2, ch3;
var pos=0;
//每三个字符进行编码。
while(pos+3<=src.length){
ch1=src.charCodeAt(pos++);
ch2=src.charCodeAt(pos++);
ch3=src.charCodeAt(pos++);
str.push(this.enKey.charAt(ch1>>2), this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f));
str.push(this.enKey.charAt(((ch2<<2)+(ch3>>6))&0x3f), this.enKey.charAt(ch3&0x3f));
}
//给剩下的字符进行编码。
if(pos<src.length){
ch1=src.charCodeAt(pos++);
str.push(this.enKey.charAt(ch1>>2));
if(pos<src.length){
ch2=src.charCodeAt(pos);
str.push(this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f));
str.push(this.enKey.charAt(ch2<<2&0x3f), '=');
}else{
str.push(this.enKey.charAt(ch1<<4&0x3f), '==');
}
}
//组合各编码后的字符,连成一个字符串。
return str.join('');
},
enKey:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
deKey: new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
),
}
解密算法已经找到,但我想要的是加密算法,所以只能通过解密算法反推加密算法,于是展开了一系列烧脑操作。终于发现了加密的原理(大概可能是)。
首先说说加密的原理:
1、先用base64对视频地址进行加密。
2、在视频地址前面加上一个4位字符串,字符串要满足以下条件:
①必须是四位16进制的字符串。
②字符串的10进制必须也是一个四位整数。(这个四位整数很重要,用来确定随机字符串的插入位置和个数的)
③插入加密地址前的是四位16进制的字符串的倒序。
3、通过开头加上的4位字符串确定随机字符串以及插入的位置。(前后相应位置都加上一段随机字符串)
看着原理是不是一头大?不用急,现在慢慢来解析一下:
现在用第一张图上的字符串来说明一下解密的过程,然后就能反推加密原理:
base64混淆加密后的地址:b901aHR0TDcDovL212dmlkZW8xLm1laXR1ZGF0YS5jb20vNTlhZmU3MDRmMTBiOTQ3ODIubXA0P2s9NDNiNzhkYjZmYmE1ZjNlZWM4NjY3NTM2MTkxNjI3ZGUmdD01OWIzNjdejNA==
①首先前面4位16进制的字符串为b901,因为是倒序添加的,所以实际上为109b。
②109b对应的10进制为4251。
③通过4251推算,前面添加的随机字符串位置为第4个字符开始,添加2个随机字符串;后台添加的随机字符串位置为倒数第5个添加1个随机字符串。
得出上面的混淆加密的随机字符为(用*号替代):
aHR0**cDovL212dmlkZW8xLm1laXR1ZGF0YS5jb20vNTlhZmU3MDRmMTBiOTQ3ODIubXA0P2s9NDNiNzhkYjZmYmE1ZjNlZWM4NjY3NTM2MTkxNjI3ZGUmdD01OWIzNjd*jNA==
去掉*号的内容即为真实的base64加密地址:aHR0cDovL212dmlkZW8xLm1laXR1ZGF0YS5jb20vNTlhZmU3MDRmMTBiOTQ3ODIubXA0P2s9NDNiNzhkYjZmYmE1ZjNlZWM4NjY3NTM2MTkxNjI3ZGUmdD01OWIzNjdjNA==
然后就可以通过普通的base64解密方法来解密视频地址了。
decodeURIComponent(escape(window.atob('aHR0cDovL212dmlkZW8xLm1laXR1ZGF0YS5jb20vNTlhZmU3MDRmMTBiOTQ3ODIubXA0P2s9NDNiNzhkYjZmYmE1ZjNlZWM4NjY3NTM2MTkxNjI3ZGUmdD01OWIzNjdjNA==')));
//得出内容为:http://mvvideo1.meitudata.com/59afe704f10b94782.mp4?k=43b78db6fba5f3eec8667536191627de&t=59b367c4
知道了加密原理,那么推算出加密算法就只是时间问题了。
。。。又经过一系列的测试整理,终于出版了js版的base64随机字符混淆加密、解密方法,如下:
;(function(base64){
var substring='substring',
split='split',
reverse='reverse',
join='join',
toString='toString',
substr='substr',
replace='replace',
fn={
getHex: function(str) {//获取前4位标记数字
return {
str: str[substring](4),//排除前4位字符串
hex: str[substring](0, 4)[split]("")[reverse]()[join]("")//前4位倒序
}
},
getDec: function(str) {//获取混淆字符位置坐标
str = parseInt(str, 16)[toString]();//前4位倒序的16进制
//str[substring](0, 2)[split]("");
return {
pre: str[substring](0, 2)[split](""),//前面坐标
tail: str[substring](2)[split]("")//后面坐标
}
},
delStr: function(str, pos) {//混淆的字符抽取
var s = str[substring](0, pos[0]),
del = str[substr](pos[0], pos[1]);//需替换的字符
return s + str[substring](pos[0])[replace](del, "");//返回替换完成后的base64字符串
},
getPos: function(str, pos) {
return [str.length - pos[0] - pos[1],pos[1]];
},
decode: function(str) {//解密
var sh = this.getHex(str),//获取前4位标记数字
pos = this.getDec(sh.hex),//获取混淆位置坐标
d = this.delStr(sh.str, pos.pre);//前面混淆的字符抽取
d=this.delStr(d, this.getPos(d, pos.tail));
return decodeURIComponent(escape(this.atob(d)));//base64转成utf-8(兼容中文) atob
},
encode:function(str){//加密
var base64=this.btoa(unescape(encodeURIComponent(str))),//转换成base64格式
random=this.getRanNum(base64),//获取16进制是4位数的随机字符
pos = this.getDec(random);//获取混淆位置坐标
base64 = this.addStr(base64, pos);//插入混淆字符
//console.log(random,pos)
return random[toString]()[split]("")[reverse]()[join]("")+base64;
},
addStr: function(str, pos) {//混淆的字符插入
var r1=this.getRanStr(pos.pre[1]),//获取随机字符串(前)
r2=this.getRanStr(pos.tail[1]),//获取随机字符串(后)
pre=this.insertStr(str,r1,pos.pre[0]),//插入随机字符串(前)
tail=pre.length - pos.tail[0];
str=this.insertStr(pre,r2,tail);//插入随机字符串(后)
return str;
},
atob:function(src){//解密
//用一个数组来存放解码后的字符。
var str=new Array();
var ch1, ch2, ch3, ch4;
var pos=0;
//过滤非法字符,并去掉'='。
src=src.replace(/[^A-Za-z0-9\+\/]/g, '');
//decode the source string in partition of per four characters.
while(pos+4<=src.length){
ch1=this.deKey[src.charCodeAt(pos++)];
ch2=this.deKey[src.charCodeAt(pos++)];
ch3=this.deKey[src.charCodeAt(pos++)];
ch4=this.deKey[src.charCodeAt(pos++)];
str.push(String.fromCharCode(
(ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2), (ch3<<6&0xff)+ch4));
}
//给剩下的字符进行解码。
if(pos+1<src.length){
ch1=this.deKey[src.charCodeAt(pos++)];
ch2=this.deKey[src.charCodeAt(pos++)];
if(pos<src.length){
ch3=this.deKey[src.charCodeAt(pos)];
str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2)));
}else{
str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4)));
}
}
//组合各解码后的字符,连成一个字符串。
return str.join('');
},
btoa:function(src){//加密
//用一个数组来存放编码后的字符,效率比用字符串相加高很多。
var str=new Array();
var ch1, ch2, ch3;
var pos=0;
//每三个字符进行编码。
while(pos+3<=src.length){
ch1=src.charCodeAt(pos++);
ch2=src.charCodeAt(pos++);
ch3=src.charCodeAt(pos++);
str.push(this.enKey.charAt(ch1>>2), this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f));
str.push(this.enKey.charAt(((ch2<<2)+(ch3>>6))&0x3f), this.enKey.charAt(ch3&0x3f));
}
//给剩下的字符进行编码。
if(pos<src.length){
ch1=src.charCodeAt(pos++);
str.push(this.enKey.charAt(ch1>>2));
if(pos<src.length){
ch2=src.charCodeAt(pos);
str.push(this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f));
str.push(this.enKey.charAt(ch2<<2&0x3f), '=');
}else{
str.push(this.enKey.charAt(ch1<<4&0x3f), '==');
}
}
//组合各编码后的字符,连成一个字符串。
return str.join('');
},
insertStr:function(str,addstr,pos){//往指定位置插入字符串
return str[substring](0,pos)+addstr+str[substring](pos);
},
getRanNum:function(str){//获取16进制是4位数的4位随机字符
var ranArr=[];
;(function(){
var n='',
length=str.length;
/** 4101开始16进制是4位数 **/
for(var i=4101;i<=9999;i++){//找出所有符合要求的16进制4位数
n=i[toString](16);//10转成16
if(length>=8&&!(Math.floor(i/100)%10===0||i%10===0)&&n.length===4){
//正常的base64编码长度大于8才前后加混淆字符
//console.log(i,n);
if(Math.floor(i/1000)<=length/2&&Math.floor(i%100/10)<=length/2){//混淆位置不能大于长度一半
ranArr.push(n);
}
}else if(i%100===0&&n.length===4){//只在前面插入混淆字符
if(Math.floor(i/1000)<=length){//混淆位置不能大于长度
ranArr.push(n);
}
}
}
}());
var length=ranArr.length,
ran = Math.round(Math.random()*(length-1));
return ranArr[ran];
},
enKey:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
deKey: new Array(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
),
getRanStr:function(num){//获取指定个数随机字符串
var key=this.enKey.split("");
length=key.length,
res = "";
for(; num-- ;) {
var id = Math.round(Math.random()*(length-1));
res += key[id];
}
return res;
}
}
base64.tranCode=fn;
window.base64=base64;
}(window.base64||{}));
以上代码已经带有一些注释,就不详细说明了。不明白或有指教的请留言吧!
另附上php版的加密算法:
class base64{
public function encode($str){//加密
if($str!=''){
$base64=base64_encode($str);
$random=$this->getRanNum($base64);//获取16进制是4位数的随机字符
$pos = $this->getDec($random);//获取混淆位置坐标
$base64 = $this->addStr($base64, $pos);//插入混淆字符
return join('',array_reverse(str_split($random))).$base64;
}
return null;
}
private function getRanNum($str){
$length=strlen($str);
$ranArr=array();
/** 4101开始16进制是4位数 **/
for($i=4101;$i<=9999;$i++){//找出所有符合要求的16进制4位数
$n=dechex($i);//10转成16
if($length>=8&&!(floor($i/100)%10===0||$i%10===0)&&strlen($n)===4){
//正常的base64编码长度大于8才前后加混淆字符
if(floor($i/1000)<=$length/2&&floor($i%100/10)<=$length/2){//混淆位置不能大于长度一半
array_push($ranArr,$n);
}
}else if($i%100===0&&strlen($n)===4){//只在前面插入混淆字符
if(floor($i/1000)<=$length){//混淆位置不能大于长度
array_push($ranArr,$n);
}
}
}
$ran = rand(0,count($ranArr)-1);
return $ranArr[$ran];
}
private function getDec($str){
$str = hexdec($str);//前4位倒序的16进制
return [
"pre"=> str_split(substr($str,0,2)),//前面坐标
"tail"=> str_split(substr($str,2))//后面坐标
];
}
private function addStr($str, $pos){
$r1=$this->getRanStr($pos["pre"][1]);//获取随机字符串(前)
$r2=$this->getRanStr($pos["tail"][1]);//获取随机字符串(后)
$pre=$this->insertStr($str,$r1,$pos["pre"][0]);//插入随机字符串(前)
$tail=strlen($pre) - $pos["tail"][0];
$str=$this->insertStr($pre,$r2,$tail);//插入随机字符串(后)
return $str;
}
private function getRanStr($num){//获取指定个数随机字符串
$enKey="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
$str=str_split($enKey);
$length=count($str);
$res = "";
for(; $num-- ;) {
$id = rand(0,$length-1);
$res .= $str[$id];
}
return $res;
}
private function insertStr($str,$addstr,$pos){
return substr($str,0,$pos).$addstr.substr($str,$pos);
}
}
算法兼容对美拍视频地址的解密!同时兼容其他内容的加密,同时兼容中文的加密、解密。
反推加密算法的过程是烧脑的,但也是有意思的,在成功的那一瞬间还是有点小兴奋的。
写了一个demo,有兴趣的可以下载看看,如图。下载地址为:https://github.com/zhouxitian/base64
欢迎指教!!!
以上代码只为研究学习使用。