使用Node crypto模块实现前后端数据加密
前言
-
这篇文章主要通过
非对称加密
和HMACSHA256
来进行数据加密的 -
在我们实际开发过程中,在进行get、post请求的时候通常会直接把明文数据传到后台。当然这在开发公司后台管理系统中没有任何问题
-
如果我们在开发对外使用的网页时,依旧使用明文传输数据,辣么就很容易被不法分子拦击篡改。
-
前方高能,请看下图
- 比如我们发送了个请求,想要获取的效果是酱样滴(神仙姐姐)
- 就在请求响应的过程中不法分子通过一系列骚操作拦截到了我们的响应
- 并且对拦截到的信息做出了修改,并返回给了前端
- 当用户看到这张图片,瞬间就崩溃了,天啊,还我神仙姐姐
- 比如我们发送了个请求,想要获取的效果是酱样滴(神仙姐姐)
-
这种恶搞还不是最可怕的,可怕的是当执行转账操作的时候,不法分子在请求的过程中篡改了数据,改成了给自己转一个亿(先定他个小目标)。这岂不就完犊子了。这个时候心情应该是酱样滴
-
保不准老板还会赏你一个N+1
-
这个时候想想大家已经明白了数据加密的重要性,接下来我们就聊聊在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
- 当我们看到这个页面就证明第一步大功告成了
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