Javascript網絡編程(加密/簽名/字節流/gzip)

轉載自:https://blog.csdn.net/weixin_30750335/article/details/96277139

Javascript網絡編程常用的兩種方式

短連接xmlhttprequest

長連接websocket

都需要考慮安全性

以下總結兩個項目中所使用的相關技術

 

傳輸類型

xmlhttprequest

xmlhttprequest.responseType

"text"(默認)

"json"(對象)

"arraybuffer"(二進制字節流)

服務端選擇UTF-8編碼返回JSON字符串

因此在不加密的情況下

可以設置responseType = "json"直接解析得到對象

 

websocket

websocket可以接收文本、ArrayBuffer、Blob類型的對象

取決於服務端推送的真實數據類型

wss.onmessage = function(msg) {
    if(msg.data instanceof ArrayBuffer) // ...
    if(msg.data instanceof Blob)        // ...
    if(typeof msg.data == 'string')     // ...
}

 

websocket可以在向服務端傳輸數據時選擇文本或字節流

文本十分簡單就不提了

字節流傳輸可以自行編輯頭部結構等等

注意若傳輸中文,需要先做encodeURI

var uintArray = new Uint8Array(content.length);
for (var i = 0; i < content.length; i++) {
    uintArray[i] = content.charCodeAt(i);
}
this._ws.send(uintArray.buffer);

 

加密解密

服務端客戶端共用RC4算法對接口的返回結果做加密、解密

RC4算法加密、解密共用一套代碼,首次執行加密,再次執行解密(異或)

服務端用密鑰操作明文得到密文

客戶端用密鑰操作密文得到明文

需要注意的是,網上可以找到多種RC4算法的實現

包括各種語言的版本,實現細節往往不同

而服務端客戶端往往使用不同語言

務必保證兩端的RC4算法實現完全一致(可能需要自己手寫)

隨便抄兩個版本的話往往對不上(比如網上搜到的PHP版本與JS版本就對不上)

PHP實現如下

/**
 * 相對應的 PHP 版本 RC4
 * rc4加密
 * @param string $data      數據
 * @param string $password  密碼
 * @param bool   $outBase64 是否返回base64
 * @return string
 */
public function rc4Encrypt($data, $password, $outBase64 = FALSE) {
    $cipher     = '';
    $key[]      = '';
    $box[]      = '';
    $lengthData = strlen($data);
    $lengthPass = strlen($password);
    for ($i = 0; $i < 256; $i++) {
        $key[$i] = ord($password[$i % $lengthPass]);
        $box[$i] = $i;
    }
    for ($j = $i = 0; $i < 256; $i++) {
        $j       = ($j + $box[$i] + $key[$i]) % 256;
        $tmp     = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    for ($a = $j = $i = 0; $i < $lengthData; $i++) {
        $a       = ($a + 1) % 256;
        $j       = ($j + $box[$a]) % 256;
        $tmp     = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $k       = $box[(($box[$a] + $box[$j]) % 256)];
        $cipher  .= chr(ord($data[$i]) ^ $k);
    }
    return $outBase64 ? base64_encode($cipher) : $cipher;
}

 

編碼問題

RC4編碼往往會產生亂碼

異或操作二進制文本的結果是隨機的字節流

JS中默認使用UTF-16編碼

而我們的服務端使用UTF-8編碼

xmlhttprequest.responseType == 'text' 時

收到的密文貌似是被截斷的(字節流自動轉爲文本時,由於編碼不一致而截斷)

因此需要已字節流的方式接收密文

xmlhttprequest.responseType = 'arraybuffer'

// 注意:不是xhr.responseText
arrayBuffer = xhr.response
// 對應UTF-8的字節流數組
byteArray = new Uint8Array(arrayBuffer)
// 拼裝密文
encodedString = String.fromCharCode.apply(null, byteArray)
// RC4解碼得到明文(會有中文字符亂碼問題)
decodedString = rc4Encrypt(encodedString , 'secret')
// 處理中文亂碼
escapedString = escape(decodedString)
finalString = decodeURIComponent(escapedString)
// 解析得到JSON對象
json = JSON.parse(finalString)

如果一定要用文本的方式傳輸數據的話

可能需要服務端在加密後做一遍encodeURI避免亂碼

客戶端收到密文後先做一遍decodeURI

 

簽名校驗

服務端客戶端共用一個密鑰做md5簽名運算

客戶端發起請求時在header中加入參數的md5簽名

服務端時候請求時,對參數做簽名並與header中傳入的簽名做比較

以此排除非法請求

Javascript中設置header需要處理一些跨域問題

// 在header中添加簽名
xhr.open("GET", url, true);
xhr.setRequestHeader("CONTENT-TYPE", "application/x-www-form-urlencoded");
xhr.setRequestHeader('sign',strSign);
xhr.send();
// 跨域問題服務端
// Access-Control-Allow-Headers: *
// Access-Control-Allow-Origin: *

 

JS版本的MD5算法如下

!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
//# sourceMappingURL=md5.min.js.map
//https://github.com/blueimp/JavaScript-MD5
// eg.  md5('abc')

 

數據壓縮

數據壓縮可以一定程度上減小傳輸壓力

加快傳輸速度

只需要服務端、客戶端共同開啓壓縮和解壓即可

常用的有gzip壓縮,gzip對於中文也是支持的不會產生亂碼

function uintToString(uintArray) {
    if (SOCKET.bGzip) {
        var encodedString = String.fromCharCode.apply(null, uintArray), 
        decodedString = pako.inflate(encodedString, {to: "string"});
        return decodedString;
    }
    else{
        var encodedString = String.fromCharCode.apply(null, uintArray), 
        decodedString = decodeURIComponent(escape(encodedString));
        return decodedString;
    }
}

下載地址

https://github.com/nodeca/pako/blob/master/dist/pako.min.js

 

參考文獻

https://stackoverflow.com/questions/17191945/conversion-between-utf-8-arraybuffer-and-string

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章