nodejs+vue实现前后端AES加密(更新版本)

192 阅读3分钟

后端部分

使用nodejs的express框架搭建

utils/AES.js 编写AES的加解密函数

const crypto = require('crypto');
const secretKey = '1234567890123456'; // 16 字节的密钥,必须为 16/24/32 字节
const iv = crypto.randomBytes(16); // 生成随机初始向量

// AES 加密函数
function AES_encrypt(text) {
    const cipher = crypto.createCipheriv('aes-128-cbc', Buffer.from(secretKey), iv);
    let encrypted = cipher.update(text, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return {
        iv: iv.toString('hex'),
        content: encrypted   // 返回 Base64 编码的加密内容
    };
}

// AES 解密函数
function AES_decrypt(encryptedText, ivHex) {
    console.log(encryptedText, ivHex);
    const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(secretKey), Buffer.from(ivHex, 'hex'));
    // let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
    let decrypted = decipher.update(Buffer.from(encryptedText, 'base64'), 'binary', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
}
// //测试加密
// const encryptedText = { account: '111222', password: 'booway1234' };
// const encrypted = AES_encrypt(JSON.stringify(encryptedText));
// console.log(encrypted); // { iv: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6', content: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6' }
// console.log(JSON.stringify(encrypted)); // {"iv":"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6","content":"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"}
// //测试解密
// const decryptedText = AES_decrypt(encrypted.content, encrypted.iv);
// console.log(JSON.parse(decryptedText)); // { account: '111222', password: 'booway1234' }
// console.log(decryptedText); // {"account":"111222","password":"booway1234"}

exports.AES_encrypt = AES_encrypt;
exports.AES_decrypt = AES_decrypt;

解密中间件 middleware/AES_decrypt.js

const { AES_decrypt } = require('../utils/AES')
const decryptFn = (req, res, next) => {
    console.log(req.body.content, req.body.iv)
    if (req.body && req.body.content) {
        try {
            const decryptedData = AES_decrypt(req.body.content, req.body.iv);
            req.body = JSON.parse(decryptedData); // 将解密后的数据赋给 req.body
            console.log('解密后的请求数据:', req.body);
        } catch (error) {
            console.error('解密请求数据时出错:', error);
            return res.status(400).send('Invalid content data.');
        }
    }
    next(); // 继续处理请求
}
module.exports = decryptFn;

加密中间件 middleware/AES_encrypt.js

const { AES_encrypt } = require('../utils/AES');
const secretFn = (req, res, next) => {
    const originalSend = res.send;
    // 包装 res.send 方法
    res.send = function (data) {
        if (typeof data === 'object' && data !== null) {
            // 将data转换为字符串
            const dataStr = JSON.stringify(data);
            // 对响应数据进行加密
            const encryptedResponse = AES_encrypt(dataStr);
            // console.log('加密前的数据:', dataStr);
            // console.log('加密后的数据:', encryptedResponse);
            // console.log("加密后的数据类型:" + typeof encryptedResponse)
            // 继续用原始 send 方法发送加密后的数据
            // 加密后的数据类型是对象,这里需要转换为字符串,防止循环调用sned方法
            originalSend.call(this, JSON.stringify(encryptedResponse));
        }

    };
    next();
}

module.exports = secretFn;

前端部分

vue3环境 utils/aes.js 封装AES加解密函数

import CryptoJS from 'crypto-js';

// 定义密钥,确保这个密钥安全且不被泄露
const secretKey = '1234567890123456'; // 16 字节的密钥

// AES 加密函数
export function AES_encrypt(data) {
    console.log('type', typeof data);
    // 生成随机IV
    const iv = CryptoJS.lib.WordArray.random(16); // 生成随机的16字节IV

    const encrypted = CryptoJS.AES.encrypt(
        JSON.stringify(data),
        CryptoJS.enc.Utf8.parse(secretKey),
        {
            iv: iv, // 使用随机生成的IV
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7,
        }
    );
    // 返回 IV 和加密后的内容
    return {
        iv: iv.toString(CryptoJS.enc.Hex),
        content: encrypted.toString()
    };
}

// AES 解密函数
export function AES_decrypt(encryptedData) {
    try {
        //json字符串转对象
        encryptedData = JSON.parse(encryptedData);
        const iv = CryptoJS.enc.Hex.parse(encryptedData.iv);
        // 解密
        const decrypted = CryptoJS.AES.decrypt(encryptedData.content, CryptoJS.enc.Utf8.parse(secretKey), {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7,
        });
        console.log('decrypted', decrypted.toString(CryptoJS.enc.Utf8));

        return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8)); // 返回解密后的对象
    } catch (error) {
        console.error("解密过程中出现错误:", error);
        throw new Error("无法解密数据"); // 处理解密错误
    }

}

请求和响应拦截器中

instance.interceptors.request.use(function (config) {

    // 添加请求之前的逻辑
    const token = localStorage.getItem('token')
    if (token) {
        //如果token不存在则会返回首页进行登录,这的逻辑卸载的vue的全局路由守卫中
        config.headers.Authorization = token
    }
    // AES加密
    if (config.data) {
        const encryptedData = AES_encrypt(config.data);
        config.data = encryptedData; // 将加密后的数据放入请求体
        console.log(config.data)
    }

    return config;
}, function (error) {
    // 当请求出现错误的逻辑
    return Promise.reject(error);
});

instance.interceptors.response.use(function (response) {
    // 首先需要对响应信息进行AES解密
    if (response.data) {
        const decryptedData = AES_decrypt(JSON.stringify(response.data));
        response.data = decryptedData // 将解密后的数据放入响应体
        console.log("response.data" + JSON.stringify(response.data))
        // response.data = JSON.parse(JSON.parse(decryptedData)); // 将解密后的数据放入响应体

    }
    // 对响应数据进行处理
    // 判断响应数据中的status
    if (response.data.status || response.data.message) {
        if (response.data.status == 0) {
            ElMessage({
                message: response.data.message, // 返回的message,如注册成功
                type: 'success', // 成功状态为绿色消息提示
            })
        } else {
            ElMessage.error(response.data.message) // 错误状态为红色消息提示
        }
    }
    return response.data;
}, function (error) {
    // 当响应出现错误的逻辑
    if (error && error.response) {
        switch (error.response.status) {
            case 400:
                ElMessage.error('请求错误')
                break
            case 401:
                ElMessage.error('未授权,请登录')
                break
            case 403:
                ElMessage.error('拒绝访问')
                setTimeout(() => {
                    window.location.href = '/' // 跳转到登录页面
                }, 1000);
                break
            case 404:
                ElMessage.error(`请求地址出错: ${error.response.config.url}`)
                break
            case 500:
                ElMessage.error('服务器内部错误')
                break
            default:
                ElMessage.error(`连接出错:${error.response.status}`)
        }
    }


    return Promise.reject(error);
});