敏感數據加密方案及實現

👆   這是第  73  篇 不摻水的原創 ,想要了解更多 ,請戳上方藍色字體: 政採雲前端團隊  關注咱們吧~

本文首發於政採雲前端團隊博客:敏感數據加密方案及實現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 中的全部請求,因此若是某些插件中有惡意的代碼仍是能夠獲取到用戶信息的,下面爲你們演示。

因此光采用 HTTPS,一些敏感信息若是仍是以明文的形式傳輸的話,也是不安全的。若是在 HTTPS 的基礎上再進行數據的加密,那相對來講就更好了。

加密算法介紹

  • 對稱加密

    對稱加密算法,又稱爲共享密鑰加密算法。在對稱加密算法中,使用的密鑰只有一個,發送和接收雙方都使用這個密鑰對數據進行加密和解密。

    這就要求加密和解密方事先都必須知道加密的密鑰。其優勢是算法公開、計算量小、加密速度快、加密效率高;缺點是密鑰泄露以後,數據就會被破解。通常不推薦單獨使用。根據實現機制的不一樣,常見的算法主要有 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 = [12345678910111213141516]; // 隨機產生
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 = { id0data: { param1: sendData, param2: encrypted } };

  axios({
    method"POST",
    headers: { "content-type""application/x-www-form-urlencoded" },
    url: url,
    dataJSON.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({ b1024 }); // 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({ resulttruedata: 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({
                resulttrue,
                data: aesEncrypt(
                  JSON.stringify({ userId123address"杭州" }), // 這個數據會被加密
                  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 😊

2.關注公衆號「 政採雲前端團隊」,持續爲你推送精選好文

招賢納士

政採雲前端團隊(ZooTeam),一個年輕富有激情和創造力的前端團隊,隸屬於政採雲產品研發部,Base 在風景如畫的杭州。團隊現有 40 餘個前端小夥伴,平均年齡 27 歲,近 3 成是全棧工程師,妥妥的青年風暴團。成員構成既有來自於阿里、網易的「老」兵,也有浙大、中科大、杭電等校的應屆新人。團隊在平常的業務對接以外,還在物料體系、工程平臺、搭建平臺、性能體驗、雲端應用、數據分析及可視化等方向進行技術探索和實戰,推進並落地了一系列的內部技術產品,持續探索前端技術體系的新邊界。

若是你想改變一直被事折騰,但願開始能折騰事;若是你想改變一直被告誡須要多些想法,卻無從破局;若是你想改變你有能力去作成那個結果,卻不須要你;若是你想改變你想作成的事須要一個團隊去支撐,但沒你帶人的位置;若是你想改變既定的節奏,將會是「5 年工做時間 3 年工做經驗」;若是你想改變原本悟性不錯,但老是有那一層窗戶紙的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但願參與到隨着業務騰飛的過程,親手推進一個有着深刻的業務理解、完善的技術體系、技術創造價值、影響力外溢的前端團隊的成長曆程,我以爲咱們該聊聊。任什麼時候間,等着你寫點什麼,發給 ZooTeam@cai-inc.com

本文分享自微信公衆號 - 政採雲前端團隊(Zoo-Team)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索