前端接口加密:用Crypto-JS和JSEncrypt给你的数据穿上“防弹衣”

早上好,中午好,晚上好

很多时候需要对接口进行加密处理,防止明文传输,特别是在以下几种场景中:

  • 用户登录:在用户登录时,对用户名和密码进行加密,防止明文信息在网络传输中被截获。
  • 敏感信息传输:如用户的个人信息、银行账户信息等,在传输前进行加密处理。
  • 防止数据泄露:对涉及商业机密或者个人隐私的数据进行加密,避免数据泄露造成的损失。
  • 防止篡改:通过加密确保数据在传输过程中不被篡改,保证数据的完整性。
  • 防止接口被恶意调用:通过加密签名,确保只有合法的客户端能调用接口。
  • 防御CSRF攻击:通过加密技术,使请求带有唯一性标识,防止跨站请求伪造。

这时候,如果你直接把数据"裸奔"到服务器,黑客们可能会像饿狼一样扑上来,把你的数据撕得粉碎。为了避免这种惨剧,我们需要给数据穿上"防弹衣"------也就是加密。

我敢说只要不是玩具项目,基本所有的项目都要进行接口的加密。

今天把前端的接口加密给你讲得明明白白。

原理

加密的原理其实很简单:把明文(原始数据)通过某种算法变成密文(加密后的数据),只有拥有密钥的人才能解密。

Crypto-JS和JSEncrypt

他们都是对数据进行加密,只是他们应对的场景不一样:

  • Crypto-JS:这个库支持多种加密算法,比如AES、DES、SHA-1等。你可以用它来对称加密(加密和解密用同一个密钥)或哈希(不可逆的加密)
  • JSEncrypt:这个库专门用于RSA加密,一种非对称加密算法。它有两个密钥:公钥和私钥。公钥用来加密,私钥用来解密。你可以把公钥放在前端,私钥放在服务器,这样即使黑客拿到公钥,也无法解密数据。

这是它们最核心的区别,也是决定你用谁的关键。

  • Crypto-JS

    Crypto-JS 是一个对称加密 库,支持 AES、DES、SHA 等算法。对称加密的特点是:加密和解密用同一个密钥

    • 优点:速度快,适合加密大量数据(比如整个 JSON 对象)。
    • 缺点:密钥管理是个大问题。如果密钥泄露,数据就完蛋了。
  • JSEncrypt

    JSEncrypt 是一个非对称加密 库,专门用于 RSA 加密。非对称加密的特点是:加密和解密用不同的密钥(公钥和私钥)。

    • 优点:安全性更高,公钥可以公开,私钥保密。
    • 缺点:速度慢,适合加密小量数据(比如一个密钥或短字符串)。

举个栗子

如果你要加密用户的信用卡信息,Crypto-JS 可以轻松搞定整个 JSON 对象,甚至对10M的文件加密而只需要几秒钟;而 JSEncrypt 更适合加密信用卡号这种短字符串,而对大数据量的场景会把你页面卡成幻灯片。

就如文章标题一样,他们不是死党,他们反而可以成为好基友,你可以把它们结合起来,发挥各自的优势!比如:

  1. 用 JSEncrypt 加密一个随机生成的对称密钥。
  2. 用 Crypto-JS 和这个密钥加密大量数据。
  3. 把加密后的数据和加密后的密钥一起发送到服务器。

这样,你既能享受对称加密的速度,又能拥有非对称加密的安全性,简直是"鱼与熊掌兼得"!

下面步骤结合这两个库进行接口加密。

步骤1

前端先安装这两个库

ssh 复制代码
pnpm add jsencrypt crypto-js

步骤2

在你的src/utils下新建jsencrypt.ts文件

ts 复制代码
import JSEncrypt from 'jsencrypt';
// 密钥对生成 http://web.chacuo.net/netrsakeypair

const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;

// 前端不建议存放私钥 不建议解密数据 因为都是透明的意义不大
const privateKey = import.meta.env.VITE_APP_RSA_PRIVATE_KEY;

// 加密
export const encrypt = (txt: string) => {
  const encryptor = new JSEncrypt();
  encryptor.setPublicKey(publicKey); // 设置公钥
  return encryptor.encrypt(txt); // 对数据进行加密
};

// 解密
export const decrypt = (txt: string) => {
  const encryptor = new JSEncrypt();
  encryptor.setPrivateKey(privateKey); // 设置私钥
  return encryptor.decrypt(txt); // 对数据进行解密
};

注意点:

  1. 密钥对生成:可以使用上面的网站快捷的生成公钥和私钥,分别保存到服务端和客户端。这个公钥和私钥其实可以通过openssl来生成,志哥也有一篇文章关于如何安装openssl的点这里。如果你有开发环境(localhost)需要https起开发服务器的场景可以参考。
  2. publickkey和privateKey可以保存到.env
js 复制代码
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwMCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAdwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
  1. 私钥其实不建议保存到前端,因为前端都是不安全的,正常的流程应该是前端使用公钥加密需要传输的数据,后端通过私钥进行解密,如果前端需要进行展示(如银行卡号),最好的方式是网站使用HTTPS。通过使用SSL/TLS协议,HTTPS加密了客户端和服务器之间的通信。这意味着即使数据在传输过程中被拦截,攻击者也无法读取数据内容。我这里进行演示,所以将私钥进行前端写死了。

步骤3

在你的src/utils下新建crypto.ts文件

ts 复制代码
import CryptoJS from 'crypto-js';

/**
 * 随机生成32位的字符串
 * @returns {string}
 */
const generateRandomString = () => {
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  const charactersLength = characters.length;
  for (let i = 0; i < 32; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
};

/**
 * 随机生成aes 密钥
 * @returns {string}
 */
export const generateAesKey = () => {
  return CryptoJS.enc.Utf8.parse(generateRandomString());
};

/**
 * 加密base64
 * @returns {string}
 */
export const encryptBase64 = (str: CryptoJS.lib.WordArray) => {
  return CryptoJS.enc.Base64.stringify(str);
};

/**
 * 解密base64
 */
export const decryptBase64 = (str: string) => {
  return CryptoJS.enc.Base64.parse(str);
};

/**
 * 使用密钥对数据进行加密
 * @param message
 * @param aesKey
 * @returns {string}
 */
export const encryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
  const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.toString();
};

/**
 * 使用密钥对数据进行解密
 * @param message
 * @param aesKey
 * @returns {string}
 */
export const decryptWithAes = (message: string, aesKey: CryptoJS.lib.WordArray) => {
  const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });
  return decrypted.toString(CryptoJS.enc.Utf8);
};

注意点:

  1. 使用encrypt的Aes算法进行加密和解密,其他算法可以自行探索

步骤4

请求前对数据进行加密

js 复制代码
import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto';
import { encrypt, decrypt } from '@/utils/jsencrypt';

  // 是否需要加密
  const isEncrypt = options.header?.isEncrypt == 'true';
  if (isEncrypt && (options.method === 'POST' || options.method === 'PUT')) {
    // 生成一个 AES 密钥
    const aesKey = generateAesKey();
    options.header[encryptHeader] = encrypt(encryptBase64(aesKey));
    options.data = typeof options.data === 'object' ? encryptWithAes(JSON.stringify(options.data), aesKey) : encryptWithAes(options.data, aesKey);
  }

注意点:

  1. 前端目前的请求封装都是基于axios,他提供了请求拦截和响应拦截,上面的代码放在请求拦截的封装代码里
  2. 不可能所有的接口都要加密,所以通过isEncrypt区分
  3. 只对POSTPUT请求进行加密
  4. encryptBase64函数用于将AES密钥转换为Base64编码的字符串,然后encrypt函数(可能是RSA加密)用于加密这个Base64字符串。加密后的密钥被存储在请求的头部中,encryptHeader是头部字段的名称。
  5. 最后一段代码是处理请求数据的加密。首先检查options.data是否是一个对象,如果是,则使用JSON.stringify将其转换为JSON字符串。然后,无论options.data是字符串还是其他类型,都使用encryptWithAes函数和之前生成的AES密钥来加密数据。加密后的数据将替换原始的options.data

响应对数据进行解密

js 复制代码
// 假设res是后端返回的原始数据
// 加密后的 AES 秘钥
const keyStr = res.header[encryptHeader];
// 加密
if (keyStr != null && keyStr != '') {
  const data: any = res.data;
  // 请求体 AES 解密
  const base64Str = decrypt(keyStr);
  // base64 解码 得到请求头的 AES 秘钥
  const aesKey = decryptBase64(base64Str.toString());
  // aesKey 解码 data
  const decryptData = decryptWithAes(data, aesKey);
  // 将结果 (得到的是 JSON 字符串) 转为 JSON
  res.data = JSON.parse(decryptData);
}

注意点:

  1. 上面的这段代码放在响应拦截器里
  2. 加密过程的反过来就是解密过程

志哥我想说

通过Crypto-JS和JSEncrypt的组合,你可以轻松地给前端接口数据穿上"防弹衣",让黑客们无从下手。不过,记住,安全是一个持续的过程,不是一劳永逸的。你需要时刻关注最新的安全漏洞和攻击手段,及时更新你的"防弹衣"。

最后,别忘了给你的代码加上注释,不然几个月后你可能会像看天书一样看着自己写的加密逻辑,心里默默吐槽:"这特么是我写的?"

Happy coding,愿你的数据永远安全!

相关推荐
逸Y 仙X20 分钟前
Git常见命令--助力开发
java·大数据·git·java-ee·github·idea
CL_IN31 分钟前
企业数据集成:实现高效调拨出库自动化
java·前端·自动化
caihuayuan41 小时前
PHP建立MySQL持久化连接(长连接)及mysql与mysqli扩展的区别
java·大数据·sql·spring
B站计算机毕业设计超人1 小时前
计算机毕业设计Hadoop+Spark+DeepSeek-R1大模型民宿推荐系统 hive民宿可视化 民宿爬虫 大数据毕业设计(源码+LW文档+PPT+讲解)
大数据·hadoop·爬虫·机器学习·课程设计·数据可视化·推荐算法
浪九天2 小时前
Vue 不同大版本与 Node.js 版本匹配的详细参数
前端·vue.js·node.js
(; ̄ェ ̄)。2 小时前
在nodejs中使用ElasticSearch(二)核心概念,应用
大数据·elasticsearch·搜索引擎
qianmoQ2 小时前
第五章:工程化实践 - 第三节 - Tailwind CSS 大型项目最佳实践
前端·css
一个儒雅随和的男子3 小时前
Elasticsearch除了用作查找以外,还能可以做什么?
大数据·elasticsearch·搜索引擎
椰果uu3 小时前
前端八股万文总结——JS+ES6
前端·javascript·es6
Sui_Network3 小时前
Sui 如何支持各种类型的 Web3 游戏
大数据·数据库·人工智能·游戏·web3·区块链