本文首發於政採雲前端團隊博客:敏感數據加密方案及實現html
https://www.zoo.team/article/data-encryption
前言
如今是大數據時代,須要收集大量的我的信息用於統計。一方面它給咱們帶來了便利,另外一方面一些我的信息數據在無心間被泄露,被非法分子用於推銷和黑色產業。前端
2018 年 5 月 25 日,歐盟已經強制執行《通用數據保護條例》(General Data Protection Regulation,縮寫做 GDPR)。該條例是歐盟法律中對全部歐盟我的關於數據保護和隱私的規範。這意味着我的數據必須使用假名化或匿名化進行存儲,而且默認使用盡量最高的隱私設置,以免數據泄露。node
相信你們也都不想讓本身在外面「裸奔」。因此,做爲前端開發人員也應該儘可能避免用戶我的數據的明文傳輸,儘量的下降信息泄露的風險。ios
看到這裏可能有人會說如今都用 HTTPS 了,數據在傳輸過程當中是加密的,前端就不須要加密了。其實否則,我能夠在你發送 HTTPS 請求以前,經過谷歌插件來捕獲 HTTPS 請求中的我的信息,下面我會爲此演示。因此前端數據加密仍是頗有必要的。git
數據泄露方式
中間人攻擊github
中間人攻擊是常見的攻擊方式。詳細過程能夠參見這裏:https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB。大概的過程是中間人經過 DNS 欺騙等手段劫持了客戶端與服務端的會話。web
客戶端、服務端之間的信息都會通過中間人,中間人能夠獲取和轉發二者的信息。在 HTTP 下,前端數據加密仍是避免不了數據泄露,由於中間人能夠僞造密鑰。爲了不中間人攻擊,咱們通常採用 HTTPS 的形式傳輸。算法
谷歌插件json
HTTPS 雖然能夠防止數據在網絡傳輸過程當中被劫持,可是在發送 HTTPS 以前,數據仍是能夠從谷歌插件中泄露出去。axios
由於谷歌插件能夠捕獲 Network 中的全部請求,因此若是某些插件中有惡意的代碼仍是能夠獲取到用戶信息的,下面爲你們演示。
加密算法介紹
-
對稱加密
對稱加密算法,又稱爲共享密鑰加密算法。在對稱加密算法中,使用的密鑰只有一個,發送和接收雙方都使用這個密鑰對數據進行加密和解密。
這就要求加密和解密方事先都必須知道加密的密鑰。其優勢是算法公開、計算量小、加密速度快、加密效率高;缺點是密鑰泄露以後,數據就會被破解。通常不推薦單獨使用。根據實現機制的不一樣,常見的算法主要有 AES (https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86)、ChaCha20 (https://zh.wikipedia.org/wiki/Salsa20#ChaCha20)、3DES (https://zh.wikipedia.org/wiki/3DES)等。
-
非對稱加密
非對稱加密算法,又稱爲公開密鑰加密算法。它須要兩個密鑰,一個稱爲公開密鑰 (public key),即公鑰;另外一個稱爲私有密鑰 (private key),即私鑰。
他倆是配對生成的,就像鑰匙和鎖的關係。由於加密和解密使用的是兩個不一樣的密鑰,因此這種算法稱爲非對稱加密算法。其優勢是算法強度複雜、安全性高;缺點是加解密速度沒有對稱加密算法快。常見的算法主要有 RSA (https://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95)、Elgamal (https://zh.wikipedia.org/wiki/ElGamal%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95)等。
-
散列算法
散列算法又稱散列函數、哈希函數,是把消息或數據壓縮成摘要,使得數據量變小,將數據的格式固定成特定長度的值。通常用於校驗數據的完整性,平時咱們下載文件就能夠校驗 MD5 來判斷下載的數據是否完整。常見的算法主要有 MD4 (https://zh.wikipedia.org/wiki/MD4)、MD5 (https://zh.wikipedia.org/wiki/MD5)、SHA (https://zh.wikipedia.org/wiki/SHA%E5%AE%B6%E6%97%8F) 等。
實現方案
-
方案一:若是用對稱加密,那麼服務端和客戶端都必須知道密鑰才行。那服務端勢必要把密鑰發送給客戶端,這個過程當中是不安全的,因此單單用對稱加密行不通。
-
方案二:若是用非對稱加密,客戶端的數據經過公鑰加密,服務端經過私鑰解密,客戶端發送數據實現加密沒問題。客戶端接受數據,須要服務端用公鑰加密,而後客戶端用私鑰解密。因此這個方案須要兩套公鑰和私鑰,須要在客戶端和服務端各自生成本身的密鑰。
-
方案三:若是把對稱加密和非對稱加密相結合。客戶端須要生成一個對稱加密的密鑰 1,傳輸內容與該密鑰 1進行對稱加密傳給服務端,而且把密鑰 1 和公鑰進行非對稱加密,而後也傳給服務端。服務端經過私鑰把對稱加密的密鑰 1 解密出來,而後經過該密鑰 1 解密出內容。以上是客戶端到服務端的過程。若是是服務端要發數據到客戶端,就須要把響應數據跟對稱加密的密鑰 1 進行加密,而後客戶端接收到密文,經過客戶端的密鑰 1 進行解密,從而完成加密傳輸。
-
總結:以上只是列舉了常見的加密方案。總的來看,方案二比較簡單,可是須要維護兩套公鑰和私鑰,當公鑰變化的時候,必須通知對方,靈活性比較差。方案三相對方案二來講,密鑰 1 隨時能夠變化,而且不須要通知服務端,相對來講靈活性、安全性好點而且方案三對內容是對稱加密,當數據量大時,對稱加密的速度會比非對稱加密快。因此本文采用方案三給予代碼實現。
代碼實現
-
下面是具體的代碼實現(以登陸接口爲例),主要的目的就是要把明文的我的信息轉成密文傳輸。其中對稱加密庫使用的是 AES,非對稱加密庫使用的是RSA。
-
客戶端:
-
AES 庫(aes-js):https://github.com/ricmoo/aes-js
-
RSA庫(jsencrypt):https://github.com/travist/jsencrypt
-
具體代碼實現登陸接口
一、客戶端須要隨機生成一個 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 待加密的字符串
* @param {array} key 加密key
*/
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;
}
三、請求登陸
/**
* 登錄接口
*/
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):
-
AES庫(aes-js):https://github.com/ricmoo/aes-js
-
RSA 庫(node-rsa):https://github.com/rzcoder/node-rsa
-
具體代碼實現登陸接口
一、引用加密庫
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 加密的字符串
* @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)); // 這邊使用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;
}
-
完整的示例代碼:https://github.com/Pulset/FrontDataEncrypt
演示效果
總結
本文主要介紹了一些前端安全方面的知識和具體加密方案的實現。爲了保護客戶的隱私數據,不論是 HTTP 仍是 HTTPS,都建議密文傳輸信息,讓破解者增長一點攻擊難度吧。固然數據加解密也會帶來必定性能上的消耗,這個須要各位開發者各自衡量了。
參考文獻
看完這篇文章,我奶奶都懂了 https 的原理 (https://www.cnblogs.com/sujing/p/10927569.html)
中間人攻擊 (https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB)
看完兩件事
若是你以爲這篇內容對你挺有啓發,我想邀請你幫我兩件小事
1.點個「在看」,讓更多人也能看到這篇內容(點了「在看」,bug -1 😊)
招賢納士
政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。
若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com
本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。