☰
Vue项目前端数据加密实战:AES、RSA、SHA-256与Base64深度解析
2026/9/29 2:15:17 网站建设 项目流程

1. 先说为什么前端要捣鼓数据加密这件事

做 Vue 项目做久了,你会发现一个特别尴尬的事实:前端代码本质上就是一本摊开的书,任何懂点浏览器开发者工具的人,都能看到你的请求参数、接口返回值,甚至直接把你的 JavaScript 逻辑扒个底朝天。于是很多人就会问:那前端做数据加密有什么用?反正密钥都在代码里,别人一翻就能看到,加密不就成摆设了吗?

这个问题问得没毛病,但忽略了一个核心现实:绝大多数攻击者根本不会去看你的源码,他们用的是自动化脚本、抓包工具、批量扫描器。你做个 Base64 处理,就能挡住一批只会看网络面板的菜鸟;你做个加盐的 SHA-256,就能挡住一批直接拿明文撞接口的脚本;你认真上 AES 对称加密,就能让中间人抓到的包变成一堆没法直接利用的乱码。加密不是万能盾牌,但它是一个基础的纵深防御环节,能把攻击成本抬高一大截。

这篇文章我不讲密码学论文里的高深理论,就从一个 Vue 项目实战的角度,把六种最常用的数据加密方式挨个过一遍:底层原理用大白话解释,Vue 里的集成代码直接给,适合谁用、什么时候别用也说清楚。毕竟我们写业务代码的,不是去设计加密算法,而是要学会在合适的场景里,选一把合适的锁。

2. 六种加密方式的整体认知与选型地图

2.1 先把加密、哈希、编码这三兄弟分清楚

在我开始贴代码之前,有一件事必须先掰扯清楚:很多人容易把加密、哈希、编码混为一谈,实际上它们完全是三类东西。

编码(Encoding)是可逆的,而且不需要密钥,它的目的是格式转换而不是保护数据。Base64 就属于这一类。哈希(Hashing)是不可逆的,同一个输入永远得到同一个输出,但输出无法还原回输入,它的目的是完整性校验或口令存储,MD5、SHA-256 都属于这一类。加密(Encryption)是可逆但需要密钥的,加密后的数据可以解密还原,AES、RSA 才属于这一类。

这个区分直接决定了你的选型方向。如果只是不想让数据"长得太明显",用编码;如果要验证数据有没有被篡改,用哈希;如果问你要不要用加密,那么还要细分:对称加密还是非对称加密?这两者的区别,一句话就能说清:对称加密就像一把钥匙开一把锁,加解密用同一个密钥;非对称加密是两把钥匙,公钥加密、私钥解密,公钥随便发,私钥自己藏。

2.2 六种方式快速总览与选型建议

先把六种方式摊开,给一个全局视角,后面逐一说实现细节。

加密方式类别可逆性典型用途强度等级
Base64编码可逆无密钥图片传输、URL 参数伪装、二进制转文本极低,仅防肉眼
MD5哈希不可逆旧系统口令摘要、文件校验低,已可碰撞
SHA-256哈希不可逆签名校验、口令加盐哈希中高
AES对称加密可逆需密钥请求参数加密、敏感字段加密存储高
RSA非对称加密可逆需密钥对密钥交换、登录密码传输高
加盐逻辑辅助手段不可逆配合 MD5、SHA-256 使用视配合对象而定

选型建议很简单:登录密码,要么走 RSA 传公钥加密后的密文,要么走 SHA-256 加盐哈希后直接交给后端;接口请求体要做防篡改,用 AES + 时间戳 + 签名头;URL 参数不想太扎眼,用 Base64 做一层轻量伪装。下面我逐个给你拆解,重点放在 Vue 工程里能直接跑起来的实现。

3. Base64 与 MD5:入门级方案,真不是让你拿去防黑客的

3.1 在 Vue 里用 Base64 处理传输数据

Base64 的本质是把任意二进制数据转换成由 64 个字符组成的可打印文本,它解决的问题是"兼容性"而不是"安全性"。比如你在 URL 里直接传中文参数、传图片二进制流,很可能因为字符集、编码规则不同而出问题,转成 Base64 之后就变成了data:image/png;base64,...这样的安全字符串。

Vue 项目里做 Base64 有现成工具,浏览器原生就支持两个函数:btoa用来编码,atob用来解码。注意坑点:这两个函数不支持 Unicode 字符,直接对中文调用会直接抛异常。解决方式是先做一步encodeURIComponent处理。

// 支持中文的 Base64 编码/解码工具 export const base64Encode = (str) => { return btoa(encodeURIComponent(str)) } export const base64Decode = (str) => { return decodeURIComponent(atob(str)) }

如果你用的是 Node 服务端渲染(Nuxt/SSR),或者项目里已经装了 lodash,也可以用lodash的_.base64Encode,不过我一般建议直接用原生封装,不引入额外依赖。实测下来,Base64 编码后的体积会增加大约 33%,如果接口对请求体大小有严格要求,可以配合压缩使用。

Base64 最大的好处是随手就能用,最大的问题也明显:它没有密钥,任何人拿到字符串都能解开。所以它最合适的场景是:URL 参数里的中文转码、图片压缩后的 base64 展示、把带特殊字符的参数塞进跳转链接。我只是把它归类为"加密方式"里讨论,但你必须明白,它是所有方案里最不设防的,千万别拿它当真正的安全措施。

3.2 MD5 的正确用法:校验或加盐,而不是直接存密码

MD5 曾经是哈希领域的顶流,现在却是安全领域的反面教材。它的碰撞攻击已经非常成熟——两个不同内容可以算出同一个 MD5 值,彩虹表更是让没加盐的 MD5 密码分分钟被反查。我在项目里见到过很多老系统,数据库里存的就是用户密码的裸 MD5,这类系统几乎是给攻击者送人头。

那 MD5 就完全没用了吗?也不是。它适合做文件完整性校验、版本号比对、缓存 key 生成等对安全性要求不高的场景。比如上传文件时,前端算一个 MD5,后端比对一下,就能判断这个文件是否已经存在,省一遍上传流量。

在 Vue 里算 MD5 最常用的库是js-md5,安装一句命令:

npm install js-md5
import md5 from 'js-md5' const rawPassword = '123456' const hashedPassword = md5(rawPassword) console.log(hashedPassword) // e10adc3949ba59abbe56e057f20f883e

如果你实在要在老系统里用 MD5 存口令,有一个补救方案:加盐(Salt)。盐就是一段随机字符串,拼在原文后面一起做哈希。比如md5('123456' + 'x7F2k9Qp')跟md5('123456')的结果完全两码事。这样即便攻击者拿到哈希值,也无法直接用彩虹表反查出原始密码。

但我的建议是:新项目不要再用 MD5 存密码了,最低也要上 SHA-256 加盐。MD5 充其量是你了解哈希演进的入门样本,不应成为生产环境的主角。

4. SHA-256 与 Web Crypto API:现代哈希的正确姿势

4.1 使用 crypto-js 在前端计算 SHA-256

SHA-256 属于 SHA-2 家族,输出固定 256 位,即 64 个十六进制字符。它的安全性远高于 MD5,目前没有实际可行的碰撞攻击手段,是行业公认的哈希基准线。很多支付、开放平台的验签逻辑里,就是用 SHA-256 对参数拼接串做签名。

Vue 里最顺手的库是crypto-js,它是一个老牌加密库,内置了哈希、对称加密、Hmac 等一堆能力。安装方式:

npm install crypto-js

计算 SHA-256 的代码:

import CryptoJS from 'crypto-js' const message = '这是一段需要哈希的文本' const digest = CryptoJS.SHA256(message).toString() console.log(digest) // 输出 64 位十六进制字符串

需要注意:CryptoJS.SHA256返回的是一个 WordArray 对象,必须调用.toString()才能拿到十六进制字符串。如果你需要的是 Base64 格式的摘要,可以改用CryptoJS.enc.Base64.stringify(CryptoJS.SHA256(message))。

4.2 优先使用浏览器原生 Web Crypto API

除了引入 crypto-js,现代浏览器其实已经内置了 Web Crypto API,通过window.crypto.subtle暴露异步哈希能力,完全不需要额外依赖,而且底层使用原生实现,性能比纯 JavaScript 库好很多。Vue 3 项目里可以这样封装:

// utils/sha256.js export async function sha256Hex(text) { const encoder = new TextEncoder() const data = encoder.encode(text) const hashBuffer = await crypto.subtle.digest('SHA-256', data) const hashArray = Array.from(new Uint8Array(hashBuffer)) return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') }

使用时:

import { sha256Hex } from '@/utils/sha256' const sign = await sha256Hex(rawData + secretKey)

这里有一个容易踩的坑:Web Crypto API 的subtle属性只在安全上下文(HTTPS 或 localhost)中可用。你把项目部署到 HTTP 协议的测试环境,crypto.subtle会是 undefined。解决方案是降级到 crypto-js,或者在开发环境用 HTTPS。

签名场景里,SHA-256 的常见用法是"参数排序 + 拼接密钥 + 哈希"。比如你有{ name: '张三', age: 25 },先把 key 排序得到age=25&name=张三,再在末尾拼上约定的 secret key,整体做 SHA-256,得到的哈希值作为签名头传给后端。后端用同样的规则重算一遍,就能判断参数有没有被篡改。

5. AES 对称加密:请求参数加密的主力方案

5.1 理解 AES 的工作模式:CBC 和 GCM 怎么选

AES(Advanced Encryption Standard)是当前最主流的对称加密算法,密钥长度支持 128、192、256 位。它本身是一个分组密码算法,加密时把数据分成固定长度的块来运算,所以需要配合工作模式来使用。

前端最常接触的是 CBC(Cipher Block Chaining)和 GCM(Galois/Counter Mode)。CBC 模式下,每个明文块在加密前会与前一个密文块做异或运算,因此需要初始化向量(IV)来保证第一个块的安全性。GCM 模式则在加密的同时生成认证标签,可以同时保证机密性和完整性。

结论先行:新项目优先用 GCM。它自带认证能力,能发现密文被篡改;而 CBC 不具备这个能力,你还需要额外叠加 HMAC 做完整性校验。但在国内很多现有后端架构中,CBC 依然大量存在,因为一些网关联调协议写死了 CBC。下面两种模式我都贴出 Vue 集成代码。

5.2 在 Vue 中用 crypto-js 实现 AES 加解密

先看 CBC 模式,这也是你在旧项目里最常见的写法。需要准备三个变量:密钥 key、偏移量 iv、密文输出格式。密钥和 IV 的长度有讲究:AES-128 对应 16 字节密钥,AES-256 对应 32 字节密钥。

import CryptoJS from 'crypto-js' // 密钥和 IV 实际应从配置中心获取或由后端下发 const AES_KEY = CryptoJS.enc.Utf8.parse('0123456789abcdef0123456789abcdef') const AES_IV = CryptoJS.enc.Utf8.parse('abcdef9876543210') export function aesEncrypt(plaintext) { const encrypted = CryptoJS.AES.encrypt(plaintext, AES_KEY, { iv: AES_IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) return encrypted.toString() } export function aesDecrypt(ciphertext) { const decrypted = CryptoJS.AES.decrypt(ciphertext, AES_KEY, { iv: AES_IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) return decrypted.toString(CryptoJS.enc.Utf8) }

这段代码有必须强调的注意点:CryptoJS.AES.encrypt的第一个参数也可以是字符串,如果直接传字符串,库内部会把它当成 UTF-8 编码处理。而密钥和 IV 如果用字符串直接传,同样会被当成 UTF-8 处理,所以很多文章会建议你显式CryptoJS.enc.Utf8.parse(),这是对的,能避免某些 WordArray 尺寸对不上的问题。

GCM 模式在 crypto-js 里用起来略有不同,crypto-js 对 GCM 的支持是通过CryptoJS.AES.encrypt配合CryptoJS.mode.GCM实现的,但这个库对 GCM 的成熟度不如专用库。我建议你在用 GCM 时直接上 Web Crypto API:

async function aesGcmEncrypt(plaintext, key) { const encoder = new TextEncoder() const iv = crypto.getRandomValues(new Uint8Array(12)) const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, encoder.encode(plaintext) ) // 需要把 iv 和密文一起传给后端,因为解密时要同样的 iv return { iv: Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join(''), ciphertext: Array.from(new Uint8Array(encrypted)) .map(b => b.toString(16).padStart(2, '0')).join('') } }

AES 加密的实际应用场景非常多:登录时对密码做 AES 加密再传输、整个请求体加密、把用户手机号身份证号等敏感字段加密后再存入前端缓存。但必须记住,AES 的安全性完全取决于密钥管理。密钥藏在前端代码里,只是提高了破解成本,不是绝对安全。

6. RSA 非对称加密:敏感数据最后一道硬防线

6.1 非对称加密为什么更安全

RSA 是目前最经典的公钥密码体制,核心思想是"公钥加密、私钥解密"。公钥可以公开给任何人,私钥只能由持有者保管。前端用公钥加密,后端用私钥解密,即便前端代码被完全扒走,攻击者拿到的也只是公钥,没有任何解密能力。这就是 RSA 跟前端 AES 最大的区别:AES 密钥一旦泄露,加密就形同虚设;RSA 的公钥泄露,不影响加密安全。

代价也很明显:RSA 的加密长度受限,1024 位密钥最多只能加密约 117 字节的明文,2048 位密钥大约是 245 字节。所以 RSA 很少用来加密大段文本,而是用于加密 AES 的密钥、登录密码等短小敏感信息。混合密码体制的标准做法是:用 RSA 加密一个随机生成的 AES 密钥,再用这个 AES 密钥加密真正的业务数据。

6.2 Vue 里集成 jsencrypt

Vue 项目里集成 RSA,最常用的是jsencrypt这个库,它封装了 RSA 加密解密的过程,使用起来很简单。安装:

npm install jsencrypt

基础用法:

import JSEncrypt from 'jsencrypt' export function rsaEncrypt(plaintext, publicKey) { const encryptor = new JSEncrypt() encryptor.setPublicKey(publicKey) return encryptor.encrypt(plaintext) }

encrypt方法返回的是 Base64 编码的密文字符串,如果加密失败会返回false,所以使用时需要做判断。publicKey 一般由后端提供,可能是 PKCS#1 格式也可能是 PKCS#8 格式,jsencrypt 对两种都兼容,但在实际对接中如果发现加密报错,先检查 key 的格式有没有被转义、有没有多余的换行空格。

登录场景配合使用示例:

import { rsaEncrypt } from '@/utils/rsa' import { publicKey } from '@/config' async function handleLogin(loginForm) { const encryptedPassword = rsaEncrypt(loginForm.password, publicKey) const params = { username: loginForm.username, password: encryptedPassword, timestamp: Date.now() } // 传给后端 await loginApi(params) }

这样后端拿到的是 RSA 加密后的密码,即便请求被完整截获,攻击者也无法还原出原始密码。有一个实操经验:某些后端对 RSA 密文的处理要求是十六进制字符串而不是 Base64。jsencrypt 默认返回 Base64,如果你碰到后端密文解析失败,可以通过hex2b64或b64tohex这类转换方法处理。

RSA 在前端还有个经典用法:验证签名。后端用私钥对一段数据签名,前端用公钥验签,确保数据确实来自可信的后端,且没有被篡改。jsencrypt 也提供了对应的签名验签方法sign和verify。

6.3 混合加密:结合 AES 和 RSA 做账号体系

实际项目中很少只用一种加密手段,更常见的组合是"RSA + AES 混合加密"。流程大致这样:

  1. 前端生成一个随机的 AES 密钥
  2. 用这个 AES 密钥加密业务数据,得到密文 A
  3. 用后端提供的 RSA 公钥加密 AES 密钥本身,得到密文 B
  4. 把密文 A 和密文 B 一起发给后端
  5. 后端先用 RSA 私钥解出 AES 密钥,再用 AES 密钥解出业务数据

这样做的好处是:AES 加密速度快、适合大数据量,RSA 解决了 AES 密钥的配送问题。典型的密钥交换框架就是这么运作的。我建议如果你的项目有严格的防抓包要求,可以用这套混合方案,但前提是前端生成的 AES 密钥必须有足够的随机性——直接用crypto.getRandomValues生成。

7. 工程化封装:把加密能力整合进 Vue 项目

7.1 封装统一的加解密工具模块

在实际 Vue 项目里,不建议每个组件里直接 import CryptoJS,然后各写各的加密逻辑。更好的做法是把所有加密工具收敛到一个模块,统一导出,方便维护和替换。以 src/utils 目录为例,可以建立一个crypto.js文件,把所有方法集中管理。

// src/utils/crypto.js import CryptoJS from 'crypto-js' import JSEncrypt from 'jsencrypt' const CRYPTO_CONFIG = { aesKey: '你的AES密钥', aesIv: '你的AES偏移量', rsaPublicKey: '你的RSA公钥' } const parseKey = (key) => CryptoJS.enc.Utf8.parse(key) export const sha256 = (data) => CryptoJS.SHA256(data).toString() export const aesEncrypt = (data) => { return CryptoJS.AES.encrypt(data, parseKey(CRYPTO_CONFIG.aesKey), { iv: parseKey(CRYPTO_CONFIG.aesIv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString() } export const aesDecrypt = (ciphertext) => { const decrypted = CryptoJS.AES.decrypt(ciphertext, parseKey(CRYPTO_CONFIG.aesKey), { iv: parseKey(CRYPTO_CONFIG.aesIv), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) return decrypted.toString(CryptoJS.enc.Utf8) } export const rsaEncrypt = (plaintext) => { const encryptor = new JSEncrypt() encryptor.setPublicKey(CRYPTO_CONFIG.rsaPublicKey) const encrypted = encryptor.encrypt(plaintext) if (encrypted === false) { throw new Error('RSA 加密失败') } return encrypted }

这种集中管理的模式有很多好处。一是替换底层库时不需要改动业务代码,二是可以把密钥集中在一个地方做配置管理,三是对外暴露的方法名语义清晰,团队协作时不容易用错。

7.2 在 Axios 拦截器中接入加解密逻辑

加密工具封装好之后,下一步就是把它接入 HTTP 请求链路。Vue 项目基本都用 Axios,它的请求拦截器和响应拦截器是天然的加解密接入点。下面是一个示例:对请求参数做 AES 加密,放在encryptedData字段中,对响应数据中的密文做自动解密。

// src/utils/request.js import axios from 'axios' import { aesEncrypt, aesDecrypt } from './crypto' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) service.interceptors.request.use((config) => { if (config.data && config.encrypt) { config.data = { encryptedData: aesEncrypt(JSON.stringify(config.data)) } } return config }) service.interceptors.response.use((response) => { const res = response.data if (res.encrypted) { return aesDecrypt(res.encryptedData) } return res })

这里有两个需要留意的点。第一,AES 加密后的密文是 Base64 字符串,体积会膨胀,所以在请求体加密时,需要确认后端接收的数据结构能容纳。第二,拦截器里做加解密,如果密钥或 IV 配置有问题,会导致所有请求失败,且报错信息通常很隐晦,所以封装完一定要先写单元测试打底。

另外,并不是所有请求都需要加密。我在实际项目中会通过一个自定义配置项encrypt来按需控制,默认不加密,只有需要保护敏感数据的接口才开启。这样既能保护核心数据,又不会因为全量加密导致性能和 debug 难度双双在线。

7.3 用 Vite 环境变量管理密钥和应用开关

密钥放前端,本质上是掩耳盗铃,但既然业务要求加密,你就得尽可能把密钥放在不容易直接看到的地方。一个常见做法是放在环境变量里,Vite 项目通过import.meta.env读取。你可以创建.env.development和.env.production文件,分别配置不同环境的密钥。

# .env.production VITE_AES_KEY=production_key_here VITE_AES_IV=production_iv_here VITE_RSA_PUBLIC_KEY=production_public_key_here

代码里这样读:

const key = import.meta.env.VITE_AES_KEY

需要特别提醒:.env里的内容在前端构建时会被打包进产物,用户在浏览器里直接搜VITE_AES_KEY或者搜你密钥的特征字符串,仍然能找到。环境变量只是帮你做环境隔离,不是安全措施。对于极高安全等级的业务,更稳妥的方式是在构建时从服务端动态获取密钥,但那样又会引入新的复杂度,需要你根据项目情况权衡。

8. 请求签名与时间戳防重放:比加密本身更要紧的机制

8.1 不只是加密,还得防时间重放

加密能防窃听,但防不了重放攻击。所谓重放攻击,就是攻击者把抓到的合法请求原封不动地再发一遍,比如你登录成功后,攻击者把你的登录请求重放一次,虽然他不知道密码,但他可以让你的账号被反复登录。要防这个,就需要引入时间戳和 nonce。

具体做法是:每次请求时前端生成一个唯一的 nonce(随机字符串),加上当前时间戳,连同业务参数一起参与签名。后端收到请求后,先检查时间戳是否在有效窗口期内(比如 5 分钟内),再检查这个 nonce 是否已经使用过,最后用同样的签名算法重算签名做比对。这样既校验了参数完整性,又防止了重放。

8.2 前端签名流程完整代码

下面是在 Vue 项目中结合 SHA-256 做请求签名的完整示例:

// src/utils/sign.js import { sha256 } from './crypto' export function generateSign(params, secretKey) { // 1. 去掉空值和签名本身 const sortedKeys = Object.keys(params) .filter((key) => key !== 'sign' && params[key] !== '' && params[key] != null) .sort() // 2. 拼接成 key=value&key=value const queryString = sortedKeys .map((key) => `${key}=${params[key]}`) .join('&') // 3. 末尾拼上密钥,整体哈希 return sha256(queryString + secretKey) } export function buildSignedParams(params, secretKey) { return { ...params, timestamp: Date.now(), nonce: Math.random().toString(36).slice(2), sign: '' } } // 构造完参数后调用 generateSign 补齐 sign

实际请求中大致流程是:构造业务参数,插入 timestamp 和 nonce,调用 generateSign 得到签名,把签名塞进请求头,后端用相同算法验签。我见过很多前端团队只做加密不做签名,结果接口被脚本批量刷得飞起,后来后端加了签名校验才解决。所以签名和加密一定是配套的,它们是车子的两个轮子。

9. 常见问题与避坑指南

9.1 密钥管理:前端密钥不是保险箱里的钱

这是我必须反复强调的一个老生常谈:前端的所有密钥和算法,最终都会暴露给用户。无论是 AES 密钥、RSA 公钥,还是加盐规则,只要用户愿意,都能从代码里挖出来。所以前端的加密本质是"提高攻击成本",而不是"做到绝对安全"。真正的安全需要后端配合,敏感数据尽量不返回前端、核心操作强制二次验证、后端对关键接口做风控策略。

如果后端工程师跟你说"前端加密没啥用,传明文吧",你可以把攻击成本理论讲给他听,但别跟他在密钥安全性上抬杠。合理姿势是:承担你们该承担的防护层级,同时把真正机密的东西牢牢锁在后端。

9.2 编码问题:中文密文解密出来是一堆乱码

这是 AES 加解密里出现频率最高的问题。原因多半是解密时没有用 UTF-8 解码。crypto-js 里,解密出来的 WordArray 需要调用toString(CryptoJS.enc.Utf8)转换为字符串,如果省略了参数,默认转成 hex 字符串,自然是一堆乱码。另一个常见原因是加密前没有对原文做JSON.stringify,直接把 JavaScript 对象传进去,结果解密出来是[object Object]。

解决方式是写一个自测用例,在集成到请求链路之前先本地验证一下加解密是否往返一致。

const raw = JSON.stringify({ name: '张三', score: 99 }) const encrypted = aesEncrypt(raw) const decrypted = aesDecrypt(encrypted) console.log(decrypted === raw) // 必须为 true

9.3 密钥长度与算法不匹配

AES-128 需要 16 字节密钥,AES-256 需要 32 字节密钥。如果密钥长度不匹配,crypto-js 有时候不会直接报错,而是默默用错误的方式处理,导致加解密结果不一致。RSA 密钥长度不匹配时,jsencrypt 会直接返回 false 或抛异常。所以新项目最好在工具模块里加上长度校验逻辑,发现问题提前暴露。

另一个容易被忽略的点是秘钥字符集。密钥本身建议使用 ASCII 字符,如果用了中文作为密钥,parse 后的字节数是不可控的,容易出问题。我踩过一次,后来统一用固定长度的十六进制字符串作为密钥,立刻安静了。

9.4 加密在 Electron 或小程序等非浏览器环境中的差异

Vue 不只是跑在浏览器里,Vue 项目也可能被打包成 Electron 应用,或者作为 H5 嵌套在小程序 WebView 里。这些环境的加密 API 可能跟浏览器不一样。Electron 是 Chromium 内核,Web Crypto API 可用;小程序里crypto.subtle可能在基础库版本较低时不可用。我的建议是工具层做能力检测,优先使用原生 Web Crypto API,检测不到就降级到 crypto-js。

const supportsWebCrypto = typeof crypto !== 'undefined' && crypto.subtle

这样一套兼容方案下来,基本能覆盖大部分场景。

9.5 调试时别让加密挡住了自己的路

前端加密有个副作用:一旦启用,浏览器的开发者工具里所有请求参数都是密文,后端日志里也是密文,出了问题排查起来很费劲。我的建议是环境隔离,开发环境默认关闭加解密,或者提供一个"明文模式"的开关,通过环境变量控制。生产环境强制开启,本地调试时关掉,效率能提升一大截。

另外,在响应拦截器里做自动解密时,如果后端返回的不是密文格式(比如报错信息是纯 JSON),解密逻辑会直接抛异常,导致错误信息也显示不了。所以解密前一定要先判断是不是密文,不是就原样放行。这个判断条件可以约定为某个字段是否存在,比如response.data.encrypted === true。

9.6 阿里云等网关环境下的加密适配

如果你把 Vue 项目部署在带有 API 网关的云环境里,网关层可能已经自带加解密和签名校验能力,这时候前端重复实现一套加密,反而可能与网关逻辑冲突。我遇到过的情况是:网关要求 CORS 请求头带自定义的签名头,前端因为没在 Axios 里配置allowedHeaders,导致预检请求失败,接口 403。解决方式是在 Axios 默认配置里显式声明自定义头和Content-Type。这类适配问题不算加密本身的坑,但确实是部署加密功能时常被绊倒的一环。

10. 一个 Vue 组件里综合使用加密的小案例

前面都是零散的代码片段,很多人看完可能会觉得"道理我都懂,但组合起来还是没把握"。这里我给一个完整的业务场景:一个用户登录表单,需要把密码用 RSA 加密传输,同时给整个请求做 SHA-256 签名,防止参数被篡改和重放。

<script setup> import { reactive, ref } from 'vue' import { aesEncrypt, rsaEncrypt, sha256 } from '@/utils/crypto' import { loginApi } from '@/api/auth' import { publicKey } from '@/config/env' const form = reactive({ username: '', password: '' }) const loading = ref(false) async function handleSubmit() { loading.value = true try { const timestamp = Date.now() const nonce = Math.random().toString(36).slice(2) // 1. 密码单独用 RSA 加密 const encryptedPassword = rsaEncrypt(form.password, publicKey) // 2. 构造请求参数(用户名不需要高强度加密,但可以用 AES 包一层) const payload = { username: aesEncrypt(form.username), password: encryptedPassword, timestamp, nonce } // 3. 计算签名 const sign = sha256( `username=${payload.username}&password=${payload.password}&timestamp=${timestamp}&nonce=${nonce}&secretKey=your_secret` ) // 4. 调用接口 const res = await loginApi({ ...payload, sign }) console.log('登录成功', res) } catch (error) { console.error('登录失败', error) } finally { loading.value = false } } </script>

这个例子展示的是一个通用思路:不同敏感级别的数据用不同强度的加密方式处理,密码用非对称加密,用户名用对称加密,整体加签名。这套组合在大多数后台管理系统场景里已经足够用,而且代码结构清晰,后端也好配合。

11. 我做了这么多年 Vue 项目,关于加密的最后几句大实话

加密这个话题,前端做久了,心态很容易在两个极端之间摇摆。一端是"前端加密毫无意义,就是脱裤子放屁",另一端是"我用了 AES 256 就绝对安全了"。真实情况就是开头那句话:前端加密从来不是铜墙铁壁,它能防住的是脚本小子、批量抓包、数据在传输链路上的裸奔,防不住的是真铁了心逆向你应用的人。所以设计加密方案时,先别追求最高的加密算法强度,先想明白你要防守的是谁,他的技术水平有多高,你的核心资产值不值得他花三天时间逆向。

我在实际落地时的一个体会是:加密方式选型不如加密流程的规范来得重要。规则一:密钥配置集中管理,不许在业务代码里散落;规则二:加解密必须做自测,用一组固定数据跑通往返;规则三:加解密作为中间件或拦截器统一处理,不给业务组件接触加密细节的机会;规则四:所有加密逻辑留一个可关闭的开关,方便排查问题。守住这四条,哪怕你用的只是 AES-CBC + SHA-256 的组合,在绝大多数业务场景里也已经是很稳健的一套屏障了。

最后分享一个小技巧:Vue DevTools 的 Network 面板里看请求参数都是密文,不利于本地联调。但你可以给加密工具模块打一个条件断点,当import.meta.env.DEV为 true 时,在控制台同时输出明文和密文。这样一来开发时能看得懂,生产环境又不泄露明文,两边都不耽误。这个小操作挽救过我好多次 debug 的头发,现在顺手送给你。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询