第 73 篇原創好文~
本文首發於政採雲前端團隊博客: 敏感數據加密方案及實現
如今是大數據時代,須要收集大量的我的信息用於統計。一方面它給咱們帶來了便利,另外一方面一些我的信息數據在無心間被泄露,被非法分子用於推銷和黑色產業。javascript
2018 年 5 月 25 日,歐盟已經強制執行《通用數據保護條例》(General Data Protection Regulation,縮寫做 GDPR)。該條例是歐盟法律中對全部歐盟我的關於數據保護和隱私的規範。這意味着我的數據必須使用假名化或匿名化進行存儲,而且默認使用盡量最高的隱私設置,以免數據泄露。html
相信你們也都不想讓本身在外面「裸奔」。因此,做爲前端開發人員也應該儘可能避免用戶我的數據的明文傳輸,儘量的下降信息泄露的風險。前端
看到這裏可能有人會說如今都用 HTTPS 了,數據在傳輸過程當中是加密的,前端就不須要加密了。其實否則,我能夠在你發送 HTTPS 請求以前,經過谷歌插件來捕獲 HTTPS 請求中的我的信息,下面我會爲此演示。因此前端數據加密仍是頗有必要的。java
中間人攻擊是常見的攻擊方式。詳細過程能夠參見這裏。大概的過程是中間人經過 DNS 欺騙等手段劫持了客戶端與服務端的會話。node
客戶端、服務端之間的信息都會通過中間人,中間人能夠獲取和轉發二者的信息。在 HTTP 下,前端數據加密仍是避免不了數據泄露,由於中間人能夠僞造密鑰。爲了不中間人攻擊,咱們通常採用 HTTPS 的形式傳輸。ios
HTTPS 雖然能夠防止數據在網絡傳輸過程當中被劫持,可是在發送 HTTPS 以前,數據仍是能夠從谷歌插件中泄露出去。git
由於谷歌插件能夠捕獲 Network 中的全部請求,因此若是某些插件中有惡意的代碼仍是能夠獲取到用戶信息的,下面爲你們演示。github
因此光采用 HTTPS,一些敏感信息若是仍是以明文的形式傳輸的話,也是不安全的。若是在 HTTPS 的基礎上再進行數據的加密,那相對來講就更好了。算法
對稱加密算法,又稱爲共享密鑰加密算法。在對稱加密算法中,使用的密鑰只有一個,發送和接收雙方都使用這個密鑰對數據進行加密和解密。json
這就要求加密和解密方事先都必須知道加密的密鑰。其優勢是算法公開、計算量小、加密速度快、加密效率高;缺點是密鑰泄露以後,數據就會被破解。通常不推薦單獨使用。根據實現機制的不一樣,常見的算法主要有AES、ChaCha20、3DES等。
非對稱加密算法,又稱爲公開密鑰加密算法。它須要兩個密鑰,一個稱爲公開密鑰 (public key),即公鑰;另外一個稱爲私有密鑰 (private key),即私鑰。
他倆是配對生成的,就像鑰匙和鎖的關係。由於加密和解密使用的是兩個不一樣的密鑰,因此這種算法稱爲非對稱加密算法。其優勢是算法強度複雜、安全性高;缺點是加解密速度沒有對稱加密算法快。常見的算法主要有RSA、Elgamal等。
散列算法又稱散列函數、哈希函數,是把消息或數據壓縮成摘要,使得數據量變小,將數據的格式固定成特定長度的值。通常用於校驗數據的完整性,平時咱們下載文件就能夠校驗 MD5 來判斷下載的數據是否完整。常見的算法主要有 MD4、MD5、SHA 等。
客戶端:
具體代碼實現登陸接口
客戶端須要隨機生成一個 aesKey,在頁面加載完的時候須要從服務端請求 publicKey
let aesKey = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; // 隨機產生 let publicKey = ""; // 公鑰會從服務端獲取 // 頁面加載完以後,就去獲取公鑰 window.onload = () => { axios({ method: "GET", headers: { "content-type": "application/x-www-form-urlencoded" }, url: "http://localhost:3000/getPub", }) .then(function (result) { publicKey = result.data.data; // 獲取公鑰 }) .catch(function (error) { console.log(error); }); };
aes加密和解密方法
/** * aes加密方法 * @param {string} text 待加密的字符串
*/ function aesEncrypt(text, key) { const textBytes = aesjs.utils.utf8.toBytes(text); // 把字符串轉換成二進制數據 // 這邊使用CTR-Counter加密模式,還有其餘模式能夠選擇,具體能夠參考aes加密庫 const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); const encryptedBytes = aesCtr.encrypt(textBytes); // 進行加密 const encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes); // 把二進制數據轉成十六進制 return encryptedHex; } /** * aes解密方法 * @param {string} encryptedHex 加密的字符串 * @param {array} key 加密key */ function aesDecrypt(encryptedHex, key) { const encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex); // 把十六進制數據轉成二進制 const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); const decryptedBytes = aesCtr.decrypt(encryptedBytes); // 進行解密 const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); // 把二進制數據轉成utf-8字符串 return decryptedText; } ``` - 請求登陸 ```javascript /** * 登錄接口 */ function submitFn() { const userName = document.querySelector("#userName").value; const password = document.querySelector("#password").value; const data = { userName, password, }; const text = JSON.stringify(data); const sendData = aesEncrypt(text, aesKey); // 把要發送的數據轉成字符串進行加密 console.log("發送數據", text); const encrypt = new JSEncrypt(); encrypt.setPublicKey(publicKey); const encrypted = encrypt.encrypt(aesKey.toString()); // 把aesKey進行非對稱加密 const url = "http://localhost:3000/login"; const params = { id: 0, data: { param1: sendData, param2: encrypted } }; axios({ method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, url: url, data: JSON.stringify(params), }) .then(function (result) { const reciveData = aesDecrypt(result.data.data, aesKey); // 用aesKey進行解密 console.log("接收數據", reciveData); }) .catch(function (error) { console.log("error", error); }); } ```
服務端(Node):
具體代碼實現登陸接口
引用加密庫
const http = require("http"); const aesjs = require("aes-js"); const NodeRSA = require("node-rsa"); const rsaKey = new NodeRSA({ b: 1024 }); // key的size爲1024位 let aesKey = null; // 用於保存客戶端的aesKey let privateKey = ""; // 用於保存服務端的公鑰 rsaKey.setOptions({ encryptionScheme: "pkcs1" }); // 設置加密模式
實現login接口
http .createServer((request, response) => { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Headers", "Content-Type"); response.setHeader("Content-Type", "application/json"); switch (request.method) { case "GET": if (request.url === "/getPub") { const publicKey = rsaKey.exportKey("public"); privateKey = rsaKey.exportKey("private"); response.writeHead(200); response.end(JSON.stringify({ result: true, data: publicKey })); // 把公鑰發送給客戶端 return; } break; case "POST": if (request.url === "/login") { let str = ""; request.on("data", function (chunk) { str += chunk; }); request.on("end", function () { const params = JSON.parse(str); const reciveData = decrypt(params.data); console.log("reciveData", reciveData); // 一系列處理以後 response.writeHead(200); response.end( JSON.stringify({ result: true, data: aesEncrypt( JSON.stringify({ userId: 123, address: "杭州" }), // 這個數據會被加密 aesKey ), }) ); }); return; } break; default: break; } response.writeHead(404); response.end(); }) .listen(3000);
加密和解密方法
function decrypt({ param1, param2 }) { const decrypted = rsaKey.decrypt(param2, "utf8"); // 解密獲得aesKey aesKey = decrypted.split(",").map((item) => { return +item; }); return aesDecrypt(param1, aesKey); } /** * aes解密方法 * @param {string} encryptedHex 加密的字符串
*/ function aesDecrypt(encryptedHex, key) { const encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex); // 把十六進制轉成二進制數據 const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); // 這邊使用CTR-Counter加密模式,還有其餘模式能夠選擇,具體能夠參考aes加密庫 const decryptedBytes = aesCtr.decrypt(encryptedBytes); // 進行解密 const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); // 把二進制數據轉成字符串 return decryptedText; } /** * aes加密方法 * @param {string} text 待加密的字符串 * @param {array} key 加密key */ function aesEncrypt(text, key) { const textBytes = aesjs.utils.utf8.toBytes(text); // 把字符串轉成二進制數據 const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); const encryptedBytes = aesCtr.encrypt(textBytes); // 加密 const encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes); // 把二進制數據轉成十六進制 return encryptedHex; } ```
本文主要介紹了一些前端安全方面的知識和具體加密方案的實現。爲了保護客戶的隱私數據,不論是 HTTP 仍是HTTPS,都建議密文傳輸信息,讓破解者增長一點攻擊難度吧。固然數據加解密也會帶來必定性能上的消耗,這個須要各位開發者各自衡量了。
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com