敏感数据加密方案及实现

👆   这是第  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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索