前端敏感数据加密方案及实现

578次阅读  |  发布于4年以前

前言

现在是大数据时代,需要收集大量的个人信息用于统计。一方面它给我们带来了便利,另一方面一些个人信息数据在无意间被泄露,被非法分子用于推销和黑色产业。

2018 年 5 月 25 日,欧盟已经强制执行《通用数据保护条例》(General Data Protection Regulation,缩写作 GDPR)。该条例是欧盟法律中对所有欧盟个人关于数据保护和隐私的规范。这意味着个人数据必须使用假名化或匿名化进行存储,并且默认使用尽可能最高的隐私设置,以避免数据泄露。

相信大家也都不想让自己在外面“裸奔”。所以,作为前端开发人员也应该尽量避免用户个人数据的明文传输,尽可能的降低信息泄露的风险。

看到这里可能有人会说现在都用 HTTPS 了,数据在传输过程中是加密的,前端就不需要加密了。其实不然,我可以在你发送 HTTPS 请求之前,通过谷歌插件来捕获 HTTPS 请求中的个人信息,下面我会为此演示。所以前端数据加密还是很有必要的。

数据泄露方式

中间人攻击

中间人攻击是常见的攻击方式。详细过程可以参见这里:https://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB。大概的过程是中间人通过 DNS 欺骗等手段劫持了客户端与服务端的会话。

客户端、服务端之间的信息都会经过中间人,中间人可以获取和转发两者的信息。在 HTTP 下,前端数据加密还是避免不了数据泄露,因为中间人可以伪造密钥。为了避免中间人攻击,我们一般采用 HTTPS 的形式传输。

谷歌插件

HTTPS 虽然可以防止数据在网络传输过程中被劫持,但是在发送 HTTPS 之前,数据还是可以从谷歌插件中泄露出去。

因为谷歌插件可以捕获 Network 中的所有请求,所以如果某些插件中有恶意的代码还是可以获取到用户信息的,下面为大家演示。

所以光采用 HTTPS,一些敏感信息如果还是以明文的形式传输的话,也是不安全的。如果在 HTTPS 的基础上再进行数据的加密,那相对来说就更好了。

加密算法介绍

实现方案

代码实现

1、客户端需要随机生成一个 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);
    });
};

2、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;
}

3、请求登录

/**
 * 登陆接口
 */
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);
    });
}

1、引用加密库

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" }); // 设置加密模式

2、实现 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);

3、加密和解密方法

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;
}

演示效果

总结


本文主要介绍了一些前端安全方面的知识和具体加密方案的实现。为了保护客户的隐私数据,不管是 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)

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8