用火車頭測試采集美拍的數據時無意中發現美拍的視頻地址是一段加了混淆字符串的base64代碼。如下圖
於是好奇之下研究了下解密算法。具體過程省略800字。發現美拍的視頻解密是通過js完成,於是找到了具體的解密代碼,如下:
1 ;(function(a) { 2 var b = "substring", 3 c = "split", 4 d = "replace", 5 e = "substr", 6 f = { 7 getHex: function(a) { 8 return { 9 str: a[b](4), 10 hex: a[b](0, 4)[c]("").reverse().join("") 11 } 12 }, 13 getDec: function(a) { 14 var d = parseInt(a, 16).toString(); 15 return { 16 pre: d[b](0, 2)[c](""), 17 tail: d[b](2)[c]("") 18 } 19 }, 20 substr: function(a, c) { 21 var f = a[b](0, c[0]), 22 g = a[e](c[0], c[1]); 23 return f + a[b](c[0])[d](g, "") 24 }, 25 getPos: function(a, b) { 26 return b[0] = a.length - b[0] - b[1], 27 b 28 }, 29 decode: function(a) { 30 var b = this.getHex(a), 31 c = this.getDec(b.hex), 32 d = this[e](b.str, c.pre); 33 return window.atob(this[e](d, this.getPos(d, c.tail))) 34 } 35 }; 36 a.decodeMp4 = f, 37 window.MP = a 38 } (window.MP || {}))
通過解密代碼可以發現視頻地址字符串是base64加密的,只不過在其中插入了一些混淆字符。只要清除混淆字符即可通過base64解密得到視頻地址。
js怎樣對字符進行base64加密、解密?如下:
1 window.atob(str);//解密
2 window.btoa(str);//加密
3 //但后來發現這樣是不兼容中文的,於是有了下面的兼容中文的方法
4 decodeURIComponent(escape(window.atob(d)));//解密
5 window.btoa(unescape(encodeURIComponent(str)));//加密
由於ie兼容問題,所以另外找了個封裝好的:
1 base64={ 2 atob:function(src){//解密 3 //用一個數組來存放解碼后的字符。 4 var str=new Array(); 5 var ch1, ch2, ch3, ch4; 6 var pos=0; 7 //過濾非法字符,並去掉'='。 8 src=src.replace(/[^A-Za-z0-9\+\/]/g, ''); 9 //decode the source string in partition of per four characters. 10 while(pos+4<=src.length){ 11 ch1=this.deKey[src.charCodeAt(pos++)]; 12 ch2=this.deKey[src.charCodeAt(pos++)]; 13 ch3=this.deKey[src.charCodeAt(pos++)]; 14 ch4=this.deKey[src.charCodeAt(pos++)]; 15 str.push(String.fromCharCode( 16 (ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2), (ch3<<6&0xff)+ch4)); 17 } 18 //給剩下的字符進行解碼。 19 if(pos+1<src.length){ 20 ch1=this.deKey[src.charCodeAt(pos++)]; 21 ch2=this.deKey[src.charCodeAt(pos++)]; 22 if(pos<src.length){ 23 ch3=this.deKey[src.charCodeAt(pos)]; 24 str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2))); 25 }else{ 26 str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4))); 27 } 28 } 29 //組合各解碼后的字符,連成一個字符串。 30 return str.join(''); 31 }, 32 btoa:function(src){//加密 33 //用一個數組來存放編碼后的字符,效率比用字符串相加高很多。 34 var str=new Array(); 35 var ch1, ch2, ch3; 36 var pos=0; 37 //每三個字符進行編碼。 38 while(pos+3<=src.length){ 39 ch1=src.charCodeAt(pos++); 40 ch2=src.charCodeAt(pos++); 41 ch3=src.charCodeAt(pos++); 42 str.push(this.enKey.charAt(ch1>>2), this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f)); 43 str.push(this.enKey.charAt(((ch2<<2)+(ch3>>6))&0x3f), this.enKey.charAt(ch3&0x3f)); 44 } 45 //給剩下的字符進行編碼。 46 if(pos<src.length){ 47 ch1=src.charCodeAt(pos++); 48 str.push(this.enKey.charAt(ch1>>2)); 49 if(pos<src.length){ 50 ch2=src.charCodeAt(pos); 51 str.push(this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f)); 52 str.push(this.enKey.charAt(ch2<<2&0x3f), '='); 53 }else{ 54 str.push(this.enKey.charAt(ch1<<4&0x3f), '=='); 55 } 56 } 57 //組合各編碼后的字符,連成一個字符串。 58 return str.join(''); 59 }, 60 enKey:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 61 deKey: new Array( 62 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 63 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 65 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 66 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 67 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 68 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 69 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 70 ), 71 }
解密算法已經找到,但我想要的是加密算法,所以只能通過解密算法反推加密算法,於是展開了一系列燒腦操作。終於發現了加密的原理(大概可能是)。
首先說說加密的原理:
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解密方法來解密視頻地址了。
1 decodeURIComponent(escape(window.atob('aHR0cDovL212dmlkZW8xLm1laXR1ZGF0YS5jb20vNTlhZmU3MDRmMTBiOTQ3ODIubXA0P2s9NDNiNzhkYjZmYmE1ZjNlZWM4NjY3NTM2MTkxNjI3ZGUmdD01OWIzNjdjNA=='))); 2 //得出內容為:http://mvvideo1.meitudata.com/59afe704f10b94782.mp4?k=43b78db6fba5f3eec8667536191627de&t=59b367c4
知道了加密原理,那么推算出加密算法就只是時間問題了。
。。。又經過一系列的測試整理,終於出版了js版的base64隨機字符混淆加密、解密方法,如下:
1 ;(function(base64){ 2 var substring='substring', 3 split='split', 4 reverse='reverse', 5 join='join', 6 toString='toString', 7 substr='substr', 8 replace='replace', 9 fn={ 10 getHex: function(str) {//獲取前4位標記數字 11 return { 12 str: str[substring](4),//排除前4位字符串 13 hex: str[substring](0, 4)[split]("")[reverse]()[join]("")//前4位倒序 14 } 15 }, 16 getDec: function(str) {//獲取混淆字符位置坐標 17 str = parseInt(str, 16)[toString]();//前4位倒序的16進制 18 //str[substring](0, 2)[split](""); 19 return { 20 pre: str[substring](0, 2)[split](""),//前面坐標 21 tail: str[substring](2)[split]("")//后面坐標 22 } 23 }, 24 delStr: function(str, pos) {//混淆的字符抽取 25 var s = str[substring](0, pos[0]), 26 del = str[substr](pos[0], pos[1]);//需替換的字符 27 return s + str[substring](pos[0])[replace](del, "");//返回替換完成后的base64字符串 28 }, 29 getPos: function(str, pos) { 30 return [str.length - pos[0] - pos[1],pos[1]]; 31 }, 32 decode: function(str) {//解密 33 var sh = this.getHex(str),//獲取前4位標記數字 34 pos = this.getDec(sh.hex),//獲取混淆位置坐標 35 d = this.delStr(sh.str, pos.pre);//前面混淆的字符抽取 36 d=this.delStr(d, this.getPos(d, pos.tail)); 37 return decodeURIComponent(escape(this.atob(d)));//base64轉成utf-8(兼容中文) atob 38 }, 39 encode:function(str){//加密 40 var base64=this.btoa(unescape(encodeURIComponent(str))),//轉換成base64格式 41 random=this.getRanNum(base64),//獲取16進制是4位數的隨機字符 42 pos = this.getDec(random);//獲取混淆位置坐標 43 base64 = this.addStr(base64, pos);//插入混淆字符 44 //console.log(random,pos) 45 return random[toString]()[split]("")[reverse]()[join]("")+base64; 46 }, 47 addStr: function(str, pos) {//混淆的字符插入 48 var r1=this.getRanStr(pos.pre[1]),//獲取隨機字符串(前) 49 r2=this.getRanStr(pos.tail[1]),//獲取隨機字符串(后) 50 pre=this.insertStr(str,r1,pos.pre[0]),//插入隨機字符串(前) 51 tail=pre.length - pos.tail[0]; 52 str=this.insertStr(pre,r2,tail);//插入隨機字符串(后) 53 return str; 54 }, 55 atob:function(src){//解密 56 //用一個數組來存放解碼后的字符。 57 var str=new Array(); 58 var ch1, ch2, ch3, ch4; 59 var pos=0; 60 //過濾非法字符,並去掉'='。 61 src=src.replace(/[^A-Za-z0-9\+\/]/g, ''); 62 //decode the source string in partition of per four characters. 63 while(pos+4<=src.length){ 64 ch1=this.deKey[src.charCodeAt(pos++)]; 65 ch2=this.deKey[src.charCodeAt(pos++)]; 66 ch3=this.deKey[src.charCodeAt(pos++)]; 67 ch4=this.deKey[src.charCodeAt(pos++)]; 68 str.push(String.fromCharCode( 69 (ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2), (ch3<<6&0xff)+ch4)); 70 } 71 //給剩下的字符進行解碼。 72 if(pos+1<src.length){ 73 ch1=this.deKey[src.charCodeAt(pos++)]; 74 ch2=this.deKey[src.charCodeAt(pos++)]; 75 if(pos<src.length){ 76 ch3=this.deKey[src.charCodeAt(pos)]; 77 str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4), (ch2<<4&0xff)+(ch3>>2))); 78 }else{ 79 str.push(String.fromCharCode((ch1<<2&0xff)+(ch2>>4))); 80 } 81 } 82 //組合各解碼后的字符,連成一個字符串。 83 return str.join(''); 84 }, 85 btoa:function(src){//加密 86 //用一個數組來存放編碼后的字符,效率比用字符串相加高很多。 87 var str=new Array(); 88 var ch1, ch2, ch3; 89 var pos=0; 90 //每三個字符進行編碼。 91 while(pos+3<=src.length){ 92 ch1=src.charCodeAt(pos++); 93 ch2=src.charCodeAt(pos++); 94 ch3=src.charCodeAt(pos++); 95 str.push(this.enKey.charAt(ch1>>2), this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f)); 96 str.push(this.enKey.charAt(((ch2<<2)+(ch3>>6))&0x3f), this.enKey.charAt(ch3&0x3f)); 97 } 98 //給剩下的字符進行編碼。 99 if(pos<src.length){ 100 ch1=src.charCodeAt(pos++); 101 str.push(this.enKey.charAt(ch1>>2)); 102 if(pos<src.length){ 103 ch2=src.charCodeAt(pos); 104 str.push(this.enKey.charAt(((ch1<<4)+(ch2>>4))&0x3f)); 105 str.push(this.enKey.charAt(ch2<<2&0x3f), '='); 106 }else{ 107 str.push(this.enKey.charAt(ch1<<4&0x3f), '=='); 108 } 109 } 110 //組合各編碼后的字符,連成一個字符串。 111 return str.join(''); 112 }, 113 insertStr:function(str,addstr,pos){//往指定位置插入字符串 114 return str[substring](0,pos)+addstr+str[substring](pos); 115 }, 116 getRanNum:function(str){//獲取16進制是4位數的4位隨機字符 117 var ranArr=[]; 118 ;(function(){ 119 var n='', 120 length=str.length; 121 /** 4101開始16進制是4位數 **/ 122 for(var i=4101;i<=9999;i++){//找出所有符合要求的16進制4位數 123 n=i[toString](16);//10轉成16 124 if(length>=8&&!(Math.floor(i/100)%10===0||i%10===0)&&n.length===4){ 125 //正常的base64編碼長度大於8才前后加混淆字符 126 //console.log(i,n); 127 if(Math.floor(i/1000)<=length/2&&Math.floor(i%100/10)<=length/2){//混淆位置不能大於長度一半 128 ranArr.push(n); 129 } 130 }else if(i%100===0&&n.length===4){//只在前面插入混淆字符 131 if(Math.floor(i/1000)<=length){//混淆位置不能大於長度 132 ranArr.push(n); 133 } 134 } 135 } 136 }()); 137 var length=ranArr.length, 138 ran = Math.round(Math.random()*(length-1)); 139 return ranArr[ran]; 140 }, 141 enKey:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", 142 deKey: new Array( 143 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 144 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 145 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 146 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 147 -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 148 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 149 -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 150 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 151 ), 152 getRanStr:function(num){//獲取指定個數隨機字符串 153 var key=this.enKey.split(""); 154 length=key.length, 155 res = ""; 156 for(; num-- ;) { 157 var id = Math.round(Math.random()*(length-1)); 158 res += key[id]; 159 } 160 return res; 161 } 162 } 163 base64.tranCode=fn; 164 window.base64=base64; 165 }(window.base64||{}));
以上代碼已經帶有一些注釋,就不詳細說明了。不明白或有指教的請留言吧!
另附上php版的加密算法:
1 class base64{ 2 public function encode($str){//加密 3 if($str!=''){ 4 $base64=base64_encode($str); 5 $random=$this->getRanNum($base64);//獲取16進制是4位數的隨機字符 6 $pos = $this->getDec($random);//獲取混淆位置坐標 7 $base64 = $this->addStr($base64, $pos);//插入混淆字符 8 return join('',array_reverse(str_split($random))).$base64; 9 } 10 return null; 11 } 12 private function getRanNum($str){ 13 $length=strlen($str); 14 $ranArr=array(); 15 /** 4101開始16進制是4位數 **/ 16 for($i=4101;$i<=9999;$i++){//找出所有符合要求的16進制4位數 17 $n=dechex($i);//10轉成16 18 if($length>=8&&!(floor($i/100)%10===0||$i%10===0)&&strlen($n)===4){ 19 //正常的base64編碼長度大於8才前后加混淆字符 20 if(floor($i/1000)<=$length/2&&floor($i%100/10)<=$length/2){//混淆位置不能大於長度一半 21 array_push($ranArr,$n); 22 } 23 }else if($i%100===0&&strlen($n)===4){//只在前面插入混淆字符 24 if(floor($i/1000)<=$length){//混淆位置不能大於長度 25 array_push($ranArr,$n); 26 } 27 } 28 } 29 $ran = rand(0,count($ranArr)-1); 30 return $ranArr[$ran]; 31 } 32 private function getDec($str){ 33 $str = hexdec($str);//前4位倒序的16進制 34 return [ 35 "pre"=> str_split(substr($str,0,2)),//前面坐標 36 "tail"=> str_split(substr($str,2))//后面坐標 37 ]; 38 } 39 private function addStr($str, $pos){ 40 $r1=$this->getRanStr($pos["pre"][1]);//獲取隨機字符串(前) 41 $r2=$this->getRanStr($pos["tail"][1]);//獲取隨機字符串(后) 42 $pre=$this->insertStr($str,$r1,$pos["pre"][0]);//插入隨機字符串(前) 43 $tail=strlen($pre) - $pos["tail"][0]; 44 $str=$this->insertStr($pre,$r2,$tail);//插入隨機字符串(后) 45 return $str; 46 } 47 private function getRanStr($num){//獲取指定個數隨機字符串 48 $enKey="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 49 $str=str_split($enKey); 50 $length=count($str); 51 $res = ""; 52 for(; $num-- ;) { 53 $id = rand(0,$length-1); 54 $res .= $str[$id]; 55 } 56 return $res; 57 } 58 private function insertStr($str,$addstr,$pos){ 59 return substr($str,0,$pos).$addstr.substr($str,$pos); 60 } 61 }
算法兼容對美拍視頻地址的解密!同時兼容其他內容的加密,同時兼容中文的加密、解密。
反推加密算法的過程是燒腦的,但也是有意思的,在成功的那一瞬間還是有點小興奮的。
寫了一個demo,有興趣的可以下載看看,如圖。下載地址為:https://github.com/zhouxitian/base64
歡迎指教!!!
以上代碼只為研究學習使用。