likes
comments
collection
share

使用Node crypto模块实现前后端数据加密

作者站长头像
站长
· 阅读数 10

前言

  • 这篇文章主要通过非对称加密HMACSHA256来进行数据加密的

  • 在我们实际开发过程中,在进行get、post请求的时候通常会直接把明文数据传到后台。当然这在开发公司后台管理系统中没有任何问题

  • 如果我们在开发对外使用的网页时,依旧使用明文传输数据,辣么就很容易被不法分子拦击篡改。

  • 前方高能,请看下图

    • 比如我们发送了个请求,想要获取的效果是酱样滴(神仙姐姐) 使用Node crypto模块实现前后端数据加密
    • 就在请求响应的过程中不法分子通过一系列骚操作拦截到了我们的响应 使用Node crypto模块实现前后端数据加密
    • 并且对拦截到的信息做出了修改,并返回给了前端 使用Node crypto模块实现前后端数据加密
    • 当用户看到这张图片,瞬间就崩溃了,天啊,还我神仙姐姐
  • 这种恶搞还不是最可怕的,可怕的是当执行转账操作的时候,不法分子在请求的过程中篡改了数据,改成了给自己转一个亿(先定他个小目标)。这岂不就完犊子了。这个时候心情应该是酱样滴

  • 保不准老板还会赏你一个N+1

    使用Node crypto模块实现前后端数据加密

  • 这个时候想想大家已经明白了数据加密的重要性,接下来我们就聊聊在Nodejs中 我们要怎么处理

前置说明

  • 后台使用的技术栈是

      + express
    
  • 前端技术栈

      + vite
      + vue3
      + axios
      + jsencrypt
      + crypto-js
    

安装项目并启动

1.1、 Node端

  • 创建一个新的文件夹执行
    yarn add express
    
  • 根目录下创建app.js
       const fs  = require("fs")
       const path = require("path")
       const crypto = require("crypto") // node自带的密码相关的模块
    
       const express = require("express")
    
       const app = new express() // 实例化 express
    
       const router = express.Router() // 创建理由
    
       router.get("/api/get", (req, res) => {
           res.json([{
               name: '张三',
               age: 28
           },{
               name: '李四',
               age: 24
           }])
       })
    
    
       app.use(router) //将路由注入到 express中
    
    
       // 启动服务
       const port = 5555
       app.listen(port,() => {
           console.log("server running: 127.0.0.1:" + port);
       })
    
  • 启动项目
        nodemon ./app.js
    

1.2、前端项目启动

  • 安装 vite
        npm init @vitejs/app
    
  • 创建工程化目录,并安装依赖
        npm init @vitejs/app client --template vue
        cd client
        yarn add axios jsencrypt crypto-js
        yarn install
    
  • 修改vite.config.js添加反向代理
        import { defineConfig } from 'vite'
        import vue from '@vitejs/plugin-vue'
    
        // https://vitejs.dev/config/
        export default defineConfig({
          plugins: [vue()],
          server: {
            proxy:{
              '/api': 'http://localhost5555'
            }
          },
        })
    
    
  • src目录下创建utils/http.js设置 axios拦截器
        import axios from 'axios'
        //请求拦截
        axios.interceptors.request.use(function (config) {
            return config;
          }, function (error) {
            return Promise.reject(error);
          });
    
        // 响应拦截
        axios.interceptors.response.use(function (response) {
            return response;
          }, function (error) {
            return Promise.reject(error);
          });
    
          export default axios
    
  • 接下来再改造下app.vue
        <template>
       <div>
         <ul>
           <li v-for="(item, idx) in info.getData" :key="idx">
             <span>{{item.name}}</span>
             <span>{{item.age}}</span>
           </li>
         </ul>
       </div>
       </template>
    
       <script setup>
       import { reactive } from 'vue'
       import http from './utils/http'
       const info = reactive({
         getData: []
       })
       http({
         url: '/api/get',
         method: 'get',
         params: {
           name: '张三',
           age: '18'
         }
       }).then(res => {
         info.getData = res.data
       })
       </script>
    
    
  • 黎明前的最后一步了,启动项目访问localhost:3000
        yarn dev
    
    
  • 当我们看到这个页面就证明第一步大功告成了 使用Node crypto模块实现前后端数据加密

2、加密逻辑

  • 我们来理一下整个加密过程的逻辑。防止非法分子在请求发送、响应的过程中篡改数据
  • 首先在客户端启动的时候,发送一个请求,获取公钥,存在浏览器本地(sessionStorage、Storage)都可以,这次我们就存在sessionStorage, 然后我们发送数据的时候首先通过HMACSHA256对数据进行散列消息认证,得到一串不可逆的字符串,然后通过非对称加密的方式使用我们请求回来的公钥加密这个字符串。通过请求头发送给后台
  • 后台
    • 当用户请求公钥时,
      • 首先我们需要判断项目跟目录下是否有公钥文件,如果有直接返回公钥文件内容
      • 如果没有创建公钥和私钥,再返回公钥
    • 接下来需要写一个中间件,判断哪些路由是不需要验证的,
      • 比如获取公钥的接口就不需要验证,对不需要验证的接口直接放行
      • 对需要验证的路由,我们拿到请求头信息,用公钥对应的私钥去解密, 就会获取到HMACSHA256认证后的字符串。接下来我们用HMACSHA256对明文参数进行相同的消息认证。对比解密出来的认证字符串和自己生成的认证字符串是否一致,一致的话证明数据没有被篡改,执行放行操作,否则返回对应的错误信息提示

3、 加解密

3.1、前端通过/api/getpubkey接口获取公钥

        http({
          url: '/api/getpubkey',
          method: 'get',
        }).then(res => {
          sessionStorage.setItem("pubKey",res.data.data)
        })

3.2、后台相关代码如下

        const { execSync } = require('child_process'); //execSync方法主要是允许我们在node中写linux命令
        const resolve = _path => path.resolve(__dirname, _path)

router.get("/api/getpubkey", (req, res) => {
    //1. 读取公钥文件

    let resPublicKey = ''
    try {
        resPublicKey = fs.readFileSync(resolve('./rsa_public.key')).toString()
    } catch (error) {
            //文件不存在则创建
        execSync("openssl genrsa -out rsa_private.key 1024")
        execSync('openssl rsa -in rsa_private.key -pubout -out rsa_public.key')
        resPublicKey = fs.readFileSync(resolve('./rsa_public.key')).toString()
    }
    // 返回信息
    res.json({
        status: 0,
        data: resPublicKey
    })
})

3.3、前端在http.js中对数据加密

        import axios from 'axios'
        import jsencrypt from 'jsencrypt'
        import hmacsha256 from 'crypto-js/hmac-sha256'

        const HMACSHA256KEY = '1001'

        function hashSHA246(params) {
                    // 通过 hmacsha256 生成散列字符串
            return hmacsha256(JSON.stringify(params), HMACSHA256KEY).toString()
          }

        //请求拦截
        axios.interceptors.request.use(function (config) {
            const excludesArr = ['/api/getpubkey']
            const { url, params,data, method } = config
            if(!excludesArr.includes(url)) {
                let Authorization = ''
                if (method === "get" && params) {
                    Authorization = hashSHA246(params)
                }else if (method === "post" && data) {
                    Authorization = hashSHA246(data)
                }
                // 获取保存的公钥
                const pubKey = sessionStorage.getItem("pubKey")
                //实例化 jsencrypt
                const JSencrypt = new jsencrypt()
                // 对实例化对象设置公钥
                JSencrypt.setPublicKey(pubKey)
                // 通过公钥对数据加密
                const encrypt = JSencrypt.encrypt(Authorization)
                // 加密数据添加到请求头中
                config.headers.common['Authorization'] = encrypt
            }

            return config;
          }, function (error) {
            return Promise.reject(error);
          });

        // 响应拦截
        axios.interceptors.response.use(function (response) {
            return response;
          }, function (error) {
            return Promise.reject(error);
          });

          export default axios

3.4、node中间件的解密

    const fs = require("fs")
    const path = require("path")
    const crypto = require("crypto") // node自带的密码相关的模块
    const { execSync } = require('child_process'); //execSync方法主要是允许我们在node中写linux命令
    const express = require("express")

    const app = new express() // 实例化 express

    const router = express.Router() // 创建理由

    const resolve = _path => path.resolve(__dirname, _path)

    // 中间件
    app.use((req, res, next) => {
        const HMACSHA256KEY = '1001'
        // 过滤不需要验证的接口
        const excludesArr = ['/api/getpubkey']
        const { originalUrl } = req
        if (!excludesArr.includes(originalUrl)) {
            // 读取请求头消息
            const Authorization = req.get('Authorization')
            if (Authorization) {
                let de_res = {
                    status: 1,
                    message: '数据被篡改'
                };
                try {
                    const decryptText = crypto.privateDecrypt({
                        key: fs.readFileSync(resolve("./rsa_private.key")),
                        padding: crypto.constants.RSA_PKCS1_PADDING
                    }, Buffer.from(Authorization, "base64")).toString()
                    try {
                        if (decryptText) {
                            let hash = crypto.createHmac("sha256", HMACSHA256KEY)
                            const { method, query, body } = req
                            const obj = {
                                GET: query,
                                POST: body
                            }

                            let hashed = hash.update(JSON.stringify(obj[method])).digest("hex").toString()
                            console.log(hashed,decryptText);
                            if (hashed === decryptText) {
                                de_res = {
                                    status: 0,
                                    message: '通过'
                                }
                            } else {
                                console.log('hash摘要不相等');
                            }
                        } else {
                            console.log("缺少参数message和date");
                        }
                    } catch (error) {
                        console.log(error);
                    }
                } catch (error) {
                    console.log(error);
                }
                if(de_res.status === 0) {
                    next()
                }else {
                    res.json(de_res)
                }

            } else {
                res.json({
                    status: 1,
                    message: 'Authorization 不能为空'
                })
            }
        } else {
            //放行
            next()
        }
    })

    router.get("/api/getpubkey", (req, res) => {
        //1. 读取公钥文件

        let resPublicKey = ''
        try {
            resPublicKey = fs.readFileSync(resolve('./rsa_public.key')).toString()
        } catch (error) {
            //文件不存在则创建
            execSync("openssl genrsa -out rsa_private.key 1024")
            execSync('openssl rsa -in rsa_private.key -pubout -out rsa_public.key')
            resPublicKey = fs.readFileSync(resolve('./rsa_public.key')).toString()
        }
        // 返回信息
        res.json({
            status: 0,
            data: resPublicKey
        })
    })
    router.get("/api/get", (req, res) => {
        res.json([{
            name: '张三',
            age: 28
        }, {
            name: '李四',
            age: 24
        }])
    })


    app.use(router) //将路由注入到 express中


    // 启动服务
    const port = 5555
    app.listen(port, () => {
        console.log("server running: 127.0.0.1:" + port);
    })
  • 到这个时候请求的加密解密都写完了。妈妈再也不用担心数据请求被篡改了, 接下来就是设置防止响应篡改了

3.5、前后端请求响应验证最终极代码

  • 前端
    • http.js
        import axios from 'axios'
        import jsencrypt from 'jsencrypt'
        import hmacsha256 from  'crypto-js/hmac-sha256'
    
        const HMACSHA256KEY = '1001'
    
        function hashSHA246(params) {
                    // 通过 hmacsha256 生成散列字符串
            return hmacsha256(JSON.stringify(params), HMACSHA256KEY).toString()
          }
    
        //请求拦截
        axios.interceptors.request.use(function (config) {
            const excludesArr = ['/api/getpubkey']
            const { url, params,data, method } = config
            if(!excludesArr.includes(url)) {
                let Authorization = ''
                if (method === "get" && params) {
                    console.log(params);
                    Authorization = hashSHA246(params)
                }else if (method === "post" && data) {
                    Authorization = hashSHA246(data)
                }
                // 获取保存的公钥
                const pubKey = sessionStorage.getItem("pubKey")
                //实例化 jsencrypt
                const JSencrypt = new jsencrypt()
                // 对实例化对象设置公钥
                JSencrypt.setPublicKey(pubKey)
                console.log(Authorization);
                // 通过公钥对数据加密
                const encrypt = JSencrypt.encrypt(Authorization)
                // 加密数据添加到请求头中
                config.headers.common['Authorization'] = encrypt
            }
    
            return config;
          }, function (error) {
            return Promise.reject(error);
          });
    
        // 响应拦截
        axios.interceptors.response.use(function (response) {
            if (response.headers?.authorization) {
                const { authorization } = response.headers
                console.log(response.data);
                const decrypt = hmacsha256(JSON.stringify(response.data.data), HMACSHA256KEY).toString()
                if (decrypt === authorization) {
                  console.log("数据是安全的");
                  return response
                } else {
                  alert("数据被篡改了")
                }
    
              } else {
                return response
              }
          }, function (error) {
            return Promise.reject(error);
          });
    
          export default axios
    
    • app.vue
       <template>
      <div>
        <button @click="sendGet">get</button>
        <button @click="sendPost">post</button>
        <ul>
          <li v-for="(item, idx) in info.getData" :key="idx">
            <span>{{ item.name }}</span>
            <span>{{ item.age }}</span>
          </li>
        </ul>
      </div>
    </template>
    
    <script setup>
    import { reactive } from "vue";
    import http from "./utils/http";
    const info = reactive({
      getData: [],
    });
    http({
      url: "/api/getpubkey",
      method: "get",
    }).then((res) => {
      sessionStorage.setItem("pubKey", res.data.data);
    });
    
    function sendGet() {
      http({
        url: "/api/get",
        method: "get",
        params: {
          name: "张三",
          age: "18",
        },
      }).then((res) => {
        info.getData = res.data.data;
      });
    }
    function sendPost() {
      http({
        url: "/api/post",
        method: "post",
        data: {
          name: "张三",
          age: "18",
        },
      }).then((res) => {
        info.getData = res.data.data;
      });
    }
    </script>
    
  • 后端
    const fs = require("fs")
    const path = require("path")
    const crypto = require("crypto") // node自带的密码相关的模块
    const { execSync } = require('child_process'); //execSync方法主要是允许我们在node中写linux命令
    const express = require("express")

    const app = new express() // 实例化 express

    const router = express.Router() // 创建理由

    app.use(express.json())
    app.use(express.urlencoded({ extended: false }))

    const resolve = _path => path.resolve(__dirname, _path)
    // hmac_sha256秘钥
    const HMACSHA256KEY = '1001'
    // 中间件
    app.use((req, res, next) => {

        // 过滤不需要验证的接口
        const excludesArr = ['/api/getpubkey']
        const { originalUrl } = req
        if (!excludesArr.includes(originalUrl)) {
            // 读取请求头消息
            const Authorization = req.get('Authorization')
            if (Authorization) {
                let de_res = {
                    status: 1,
                    message: '数据被篡改'
                };
                try {
                    const decryptText = crypto.privateDecrypt({
                        key: fs.readFileSync(resolve("./rsa_private.key")),
                        padding: crypto.constants.RSA_PKCS1_PADDING
                    }, Buffer.from(Authorization, "base64")).toString()
                    try {
                        if (decryptText) {
                            let hash = crypto.createHmac("sha256", HMACSHA256KEY)
                            const { method, query, body } = req
                            const obj = {
                                GET: query,
                                POST: body
                            }

                            let hashed = hash.update(JSON.stringify(obj[method])).digest("hex").toString()
                            console.log(hashed,decryptText);
                            if (hashed === decryptText) {
                                de_res = {
                                    status: 0,
                                    message: '通过'
                                }
                            } else {
                                console.log('hash摘要不相等');
                            }
                        } else {
                            console.log("缺少参数message和date");
                        }
                    } catch (error) {
                        console.log(error);
                    }
                } catch (error) {
                    console.log(error);
                }
                if(de_res.status === 0) {
                    next()
                }else {
                    res.json(de_res)
                }

            } else {
                res.json({
                    status: 1,
                    message: 'Authorization 不能为空'
                })
            }
        } else {
            //放行
            next()
        }
    })

    router.get("/api/getpubkey", (req, res) => {
        //1. 读取公钥文件

        let resPublicKey = ''
        try {
            resPublicKey = fs.readFileSync(resolve('./rsa_public.key')).toString()
        } catch (error) {
            //文件不存在则创建
            execSync("openssl genrsa -out rsa_private.key 1024")
            execSync('openssl rsa -in rsa_private.key -pubout -out rsa_public.key')
            resPublicKey = fs.readFileSync(resolve('./rsa_public.key')).toString()
        }
        // 返回信息
        res.json({
            status: 0,
            data: resPublicKey
        })
    })
    /**
     * 通过 hmacsha256加密要返回的数据
     * @param {数据} data
     * @returns
     */
     function setResHead(data) {
        return crypto.createHmac("sha256", HMACSHA256KEY).update(JSON.stringify(data)).digest().toString("hex")
    }
    router.get("/api/get", (req, res) => {
        let data = [{
            name: '张三',
            age: 28
        }, {
            name: '李四',
            age: 24
        }]
        res.set("Authorization",setResHead(data))
        res.json({
            status:0,
            data
        })
    })

    router.post("/api/post", (req, res) => {
        let postData = [{
            name: '王五',
            age: 28
        }, {
            name: '赵六',
            age: 24
        }]
        res.set("Authorization",setResHead(postData))
        res.json({
            status:0,
            data: postData
        })
    })

    app.use(router) //将路由注入到 express中


    // 启动服务
    const port = 5555
    app.listen(port, () => {
        console.log("server running: 127.0.0.1:" + port);
    })

写在最后

  • 当你看到这里的时候,首先你是个很有毅力的人,这篇文章插图较少,基本都是干活,从头看到尾的话给自己点个赞吧
  • 这篇文章主要说了下为什么要加密,以及加密的方法
  • 以上的🌰主要是带大家入门
  • 加密这块的问题欢迎大家评论
转载自:https://juejin.cn/post/6963922820971790372
评论
请登录