敏感数据加密方案及实现

标签: 数据 加密 | 发表时间:2020-10-18 13:15 | 作者:政采云前端团队
出处:https://juejin.im/frontend

这是第 73 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客: 敏感数据加密方案及实现

前言

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

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

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

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

数据泄露方式

  • 中间人攻击

    中间人攻击是常见的攻击方式。详细过程可以参见 这里。大概的过程是中间人通过 DNS 欺骗等手段劫持了客户端与服务端的会话。

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

  • 谷歌插件

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

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

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

加密算法介绍

  • 对称加密

    对称加密算法,又称为共享密钥加密算法。在对称加密算法中,使用的密钥只有一个,发送和接收双方都使用这个密钥对数据进行加密和解密。

这就要求加密和解密方事先都必须知道加密的密钥。其优点是算法公开、计算量小、加密速度快、加密效率高;缺点是密钥泄露之后,数据就会被破解。一般不推荐单独使用。根据实现机制的不同,常见的算法主要有 AESChaCha203DES等。

  • 非对称加密

    非对称加密算法,又称为公开密钥加密算法。它需要两个密钥,一个称为公开密钥 (public key),即公钥;另一个称为私有密钥 (private key),即私钥。

他俩是配对生成的,就像钥匙和锁的关系。因为加密和解密使用的是两个不同的密钥,所以这种算法称为非对称加密算法。其优点是算法强度复杂、安全性高;缺点是加解密速度没有对称加密算法快。常见的算法主要有 RSAElgamal等。

  • 散列算法

    散列算法又称散列函数、哈希函数,是把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定成特定长度的值。一般用于校验数据的完整性,平时我们下载文件就可以校验 MD5 来判断下载的数据是否完整。常见的算法主要有 MD4MD5SHA 等。

实现方案

  • 方案一:如果用对称加密,那么服务端和客户端都必须知道密钥才行。那服务端势必要把密钥发送给客户端,这个过程中是不安全的,所以单单用对称加密行不通。

  • 方案二:如果用非对称加密,客户端的数据通过公钥加密,服务端通过私钥解密,客户端发送数据实现加密没问题。客户端接受数据,需要服务端用公钥加密,然后客户端用私钥解密。所以这个方案需要两套公钥和私钥,需要在客户端和服务端各自生成自己的密钥。

  • 方案三:如果把对称加密和非对称加密相结合。客户端需要生成一个对称加密的密钥 1,传输内容与该密钥 1进行对称加密传给服务端,并且把密钥 1 和公钥进行非对称加密,然后也传给服务端。服务端通过私钥把对称加密的密钥 1 解密出来,然后通过该密钥 1 解密出内容。以上是客户端到服务端的过程。如果是服务端要发数据到客户端,就需要把响应数据跟对称加密的密钥 1 进行加密,然后客户端接收到密文,通过客户端的密钥 1进行解密,从而完成加密传输。

  • 总结:以上只是列举了常见的加密方案。总的来看,方案二比较简单,但是需要维护两套公钥和私钥,当公钥变化的时候,必须通知对方,灵活性比较差。方案三相对方案二来说,密钥 1 随时可以变化,并且不需要通知服务端,相对来说灵活性、安全性好点并且方案三对内容是对称加密,当数据量大时,对称加密的速度会比非对称加密快。所以本文采用方案三给予代码实现。

代码实现

  • 下面是具体的代码实现(以登录接口为例),主要的目的就是要把明文的个人信息转成密文传输。其中对称加密库使用的是 AES,非对称加密库使用的是RSA。

  • 客户端:

    • AES 库(aes-js): github.com/ricmoo/aes-…

    • RSA库(jsencrypt): github.com/travist/jse…

    • 具体代码实现登录接口

      • 客户端需要随机生成一个 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): github.com/ricmoo/aes-…

    • RSA 库(node-rsa): github.com/rzcoder/nod…

    • 具体代码实现登录接口

      • 引用加密库

                 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;
        }
        复制代码
  • 完整的示例代码

演示效果

总结

本文主要介绍了一些前端安全方面的知识和具体加密方案的实现。为了保护客户的隐私数据,不管是 HTTP 还是HTTPS,都建议密文传输信息,让破解者增加一点攻击难度吧。当然数据加解密也会带来一定性能上的消耗,这个需要各位开发者各自衡量了。

参考文献

看完这篇文章,我奶奶都懂了https的原理

中间人攻击

推荐阅读

浅析 vue-router 源码和动态路由权限分配

编写高质量可维护的代码:一目了然的注释

招贤纳士

政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 40 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 [email protected]

相关 [数据 加密] 推荐:

透明数据加密

- - CSDN博客数据库推荐文章
透明数据加密                                          .                         常见问题解答. 数据在网络上依然是加密状态吗. 任何人只要获得应用程序的授权就能对数据进行解密吗. TDE 与 Oracle 提供的加密方法有何不同. 哪些加密算法可与 TDE 一同使用.

Tomcat数据库连接池数据库密码加密

- - Java - 编程语言 - ITeye博客
2、Factory中实现数据库密码解密. 3、将以上两个类打包(vajra-dbsecure.jar),并指定Main入口类. 4、tomcat全局数据源中使用加密后的数据库密码. 已有 0 人发表留言,猛击->> 这里<<-参与讨论. —软件人才免语言低担保 赴美带薪读研.

iOS中使用RSA对数据进行加密解密

- - ITeye博客
RSA算法是一种非对称加密算法,常被用于加密数据传输.如果配合上数字摘要算法, 也可以用于文件签名.. 本文将讨论如何在iOS中使用RSA传输加密数据.. openssl-1.0.1j, openssl需要使用1.x版本, 推荐使用[homebrew](http://brew.sh/)安装.. RSA使用"秘匙对"对数据进行加密解密.在加密解密数据前,需要先生成公钥(public key)和私钥(private key)..

敏感数据加密方案及实现

- - 掘金前端
这是第 73 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:. 现在是大数据时代,需要收集大量的个人信息用于统计. 一方面它给我们带来了便利,另一方面一些个人信息数据在无意间被泄露,被非法分子用于推销和黑色产业. 2018 年 5 月 25 日,欧盟已经强制执行《通用数据保护条例》(General Data Protection Regulation,缩写作 GDPR).

用 Nginx 在公网上搭建加密数据通道

- - 卡瓦邦噶!
最近在跨机房做一个部署,因为机房之间暂时没有专线,所以流量需要经过公网. 对于经过公网的流量,我们一般需要做以下的安全措施:. 只能允许已知的 IP 来访问;. 第一项很简单,一般的防火墙,或者 Iptables 都可以做到. 对于加密的部分,最近做了一些实验和学习,这篇文章总结加密的实现方案,假设读者没有 TLS 方面的背景知识,会简单介绍原理和所有的代码解释.

来自CB:大多数IT专业人员不加密客户数据

- - 膘叔
看完后才觉得,原来国内那些公司这样的习惯行为是和国外学的. 怪不得用户对于现在的互联网公司越来越不能信任了,怎么办. 特别是那些公司中存有我们帐号信息的那些. 还是以后只能到付而尽量不做在线支付. 调查结果显示,大多数遭到客户数据丢失或被盗的公司的IT专业人士表示,其用户数据并未进行加密. 根据Ponemon研究所进行、信用检查公司Experian赞助的这项研究结果显示,数据丢失包括电子邮件,信用卡或银行支付信息和社会安全号码等.

加密锁和云授权

- MArCoRQ - 月光博客
  在中国,加密锁仍然是占主导地位的软件保护方式. 近30年来,计算机硬件不知更新了多少代,软件技术也从上世纪90年代起进入了互联网时代,而加密锁保护方式却一直没有改变,顶多从原先的并口锁进化到USB锁,这是为什么呢.   这是因为,30年来,软件的盗版依然存在,人们却没有找到应对盗版更好的办法. 另外,传统的观念认为,加密锁具有安全强度高、软件授权可随加密锁移动使用优点.

Google升级HTTPS加密

- 请叫我火矞弟 - Solidot
民不拜天又不拜孔子留此膝何为 写道 "Google 修改了启用HTTPS服务的加密方法,以应对未来技术发展后可能造成的解密行为. 这项升级适用于Gmail、Docs和Google+. 现在的HTTPS实现借助于只有域名主人所掌握的私钥生成的session key来加密服务器和客户端之间的流量. 这种方法使得连接可能被所谓“追溯式解密攻击”(retrospective decryption attack)破解.

银联加密算法

- - CSDN博客推荐文章
很多人对银联卡的加密算法感兴趣,毕竟分分钟涉及的都是你的钱的安全,但网上很少人却讲银联标准加密算法. 遂写一遍当做是自己的学习笔记,偶尔忘了可以翻翻,同时希望能够帮助到其他人. 首先要认识一下cbc算法和ecb算法. cbc算法是链式的,慢,不可并行处理,但更安全,因为每一次加密都是依赖于上一次的结果,同时这也会导致一次错将导致后面的全部错误.

恐怖分子用单字母加密法加密通信

- Mathack - Solidot
英国航空公司IT雇员Rajib Karim因阴谋发动恐怖袭击而被判30年徒刑. 他利用有两千多年历史的加密方法和孟加拉国伊斯兰激进分子进行通信. 法庭被告知,与Karim联络的孟加拉国激进分子拒绝使用更先进的PGP或TrueCrypt加密技术,而宁愿使用自己发明的Excel换位表单字母替换加密法. 这种方法最早是古希腊人发明,曾被尤利乌斯·恺撒在公元前55年使用,又被称为凯撒密码,它是一种十分简单的加密技术,明文中所有字母在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文.