☰
前端数据加密全解析:Base64、MD5、AES、RSA、HMAC一次讲透
2026/9/29 1:39:41 网站建设 项目流程

前端数据加密方式这个话题,我面试过不少人,也被人问过不少次,技术群里天天有人纠结。纠结的点倒不是不会敲代码,而是对“前端加密到底防住了谁”这个前提没想清楚。很多人稀里糊涂把 Base64 当加密用、把 MD5 当万能药、把 AES 密钥写死在代码里,最后数据还是泄露了,回头怪“前端加密没用”——其实不是没用,是用错了地方、用错了姿势。

这篇文章把前端日常开发里最常用的 6 种数据加密方式一次讲透:Base64、MD5、SHA-256、AES、RSA、HMAC。每一条都会讲清原理、适用场景、完整代码、常见坑和选型逻辑。不管你是刚入行的前端新人,还是已经写了好几年业务代码的老手,只要涉及登录、支付、用户隐私、接口安全,这篇都值得认真看完。

为什么非要把这 6 种放在一起讲?因为实际项目里它们从来不是单独出现的:登录接口可能是 RSA 加密密码、AES 加密业务数据、HMAC 做接口签名、SHA-256 做完整性校验,最后还有 Base64 负责编码传输。你只有把每一块的边界弄清楚,组合起来才不会翻车。

1. 先建立正确认知:前端加密到底在防谁

1.1 一个面试官常问的“送命题”

我面试前端的时候特别爱问一个问题:“你把用户密码在前端加密之后再传给后端,能防住黑客吗?”十个人里面有八个会愣一下,然后回答“能防”。这个答案其实是错的。

前端加密防不住真正的攻击者。为什么?因为加密代码、密钥、加密过程全都在浏览器里运行,用户打开 DevTools 就能看到一切逻辑。攻击者根本不需要破解你的加密算法,他直接改你前端代码、跳过加密步骤、或者抓包复制加密后的结果再重放,都能绕过去。所以严格来说,前端加密防的不是黑客,是“随手扒数据的人”。

那为什么还要做前端加密?真实价值在于这几层:

  • 防止用户密码在传输过程中以明文形式暴露给网络链路中的第三方;
  • 防止浏览器本地存储(localStorage、IndexedDB)里的数据被直接读取;
  • 防止接口参数被随意篡改和恶意重放;
  • 给后端增加一道基础校验,提高攻击成本。

想明白这一点,你再看各种加密方案的选型,思路会清晰很多。

1.2 前端加密的四种真实用途

先不急着抄代码,我习惯把前端加密的使用场景分成四类,每一类对应的技术方案完全不同。

第一类是数据编码与展示。图片转 Base64、文件转 Base64、URL 参数编码,这类需求的目的不是保护数据,而是让数据能在特定环境里传输和展示。它需要的是可逆算法,因为要还原。很多人误把 Base64 叫成“加密”,其实是编码。

第二类是摘要与完整性校验。判断一个文件有没有被改动、一段数据有没有被篡改、一个密码摘要是否符合预期,用的是哈希算法,MD5、SHA-256 都属于这一类。特征是单向不可逆,正向算很快,反向还原几乎不可能。

第三类是数据保密存储与传输。把用户身份证号、手机号、缓存里的敏感数据变成密文,需要的时候再解开。这就要对称加密 AES 出场了,特点是快、适合大数据量,但密钥要安全保存。

第四类是身份认证与防篡改。接口签名、请求参数校验,需要保证“这条消息确实是某个持有密钥的人发出的,而且内容没被改过”,用 HMAC。

1.3 三个必须放弃的错误期望

把错误预期清理掉,后面的技术细节才有意义。

第一个错误期望:前端加密 = 绝对安全。实际上任何浏览器端加密都不可能绝对安全。网上那些“不可破解”的说法基本都是营销话术。接受这个现实,你才会认真设计后端的校验逻辑,而不是把所有安全感都寄托在加密算法上。

第二个错误期望:密钥写在前端代码里就安全。AES 的密钥、HMAC 的密钥只要出现在前端代码里,就等于公开了。前端能做的是“混淆”而不是“隐藏”,真正安全的密钥管理必须依赖后端下发给当前登录用户,或者放在 HTTPOnly Cookie 之类的受限环境里。

第三个错误期望:加密之后就再也不需要 HTTPS。前端加密解决的是“传输途中被第三方截获明文”的问题,本质上是 HTTPS 之外的第二道防线,它不能替代 HTTPS,也不能替代后端的权限校验。正确姿势是 HTTPS 保底传输安全,前端加密做重点字段的双保险,后端做最终安全边界。

这三点想通了,下面每一种加密方式你都能快速判断“我到底该不该用它、用它防什么”。

2. Base64 与 Hash 族:看似最简单,最容易用错的两个方向

2.1 Base64 只是可逆编码,不是加密

Base64 大概是前端摸得最多的“加密方式”,但把它归进加密纯粹是历史习惯。它的原理很简单:把每 3 个字节(24 bit)转成 4 个可打印字符(每个 6 bit),查表输出。所以 Base64 编码后的长度会比原数据多约 33%。

前端最常见的两个场景是图片压缩预览和接口参数传递。一个图片文件转成 Base64 字符串,可以直接塞进 img 标签的 src 属性,省一次 HTTP 请求;URL 参数里塞中文可能乱码,先 Base64 编码再传就变成纯 ASCII 字符串了。

但请你记住一点:Base64 是公开可逆的,任何人拿到编码结果都能立刻解码回原文。所以它不能用来保护手机号、身份证号、密码这类敏感信息。

原生 API 是btoa和atob。这里有个经典大坑:btoa只支持 Latin1(ISO-8859-1)字符集,直接编码中文会直接抛异常。

// 错误示范:btoa 处理中文直接报错 // btoa('你好世界') // InvalidCharacterError // 正确示范:先 encodeURIComponent 转成 UTF-8 字节串,再编码 function base64Encode(str) { return btoa(unescape(encodeURIComponent(str))); } function base64Decode(base64Str) { return decodeURIComponent(escape(atob(base64Str))); } const encoded = base64Encode('你好世界'); const decoded = base64Decode(encoded); console.log(encoded); // 5L2g5aW977yM5LiW55WM console.log(decoded); // 你好世界

unescape和escape虽然被标记为废弃 API,但在处理 Base64 中文场景下依然大量存在,兼容性极好。如果你用的是现代浏览器,也可以用TextEncoder配合String.fromCharCode来实现更标准化的方案。

2.2 MD5 的真正价值与加盐姿势

MD5 是前端最耳熟能详的哈希算法,因为历史上大量网站用它存密码。哈希的特点决定了它不可逆:给定一个输入,总能算出固定 128 位(32 个十六进制字符)的摘要,但给你摘要,你算不出原始输入。

但 MD5 有两个致命短板。一个是碰撞攻击——现在已经能在合理成本内构造出两个不同输入但 MD5 相同的文件;另一个是彩虹表——攻击者提前把海量常见密码的 MD5 结果算好存表,拿到你的摘要一查就知道原文是什么。

所以在今天的实践里,MD5 已经不适合作为密码存储的最终方案。但有两个场景依然好用:

一是文件完整性校验。上传下载大文件,先算出 MD5,下载完再算一次,一致说明文件没损坏。

二是配合后端对请求参数做校验签名(注意这里是辅助角色,真正优先选 HMAC)。

如果某些老系统还在用 MD5 做密码摘要,必须加盐。盐分成两种思路:

// 固定盐:所有用户同一个盐,防御力度弱,攻击者可以针对性建表 const FIXED_SALT = 'salt-2024'; function md5FixedSalt(password) { return md5(`${FIXED_SALT}${password}${FIXED_SALT}`); } // 随机盐:每个用户一个盐,用户注册时生成,一起存库,推荐 function md5RandomSalt(password) { const salt = Math.random().toString(36).slice(2, 10); return { salt, hash: md5(`${salt}${password}`) }; }

使用 crypto-js 的模块化导入写法:

import md5 from 'crypto-js/md5'; const hash = md5('123456').toString(); // e10adc3949ba59abbe56e057f20f883e

2.3 SHA 系列的选型与原生实现

SHA 系列是 MD5 的接班人家族。SHA-1因为碰撞漏洞已经被主流浏览器和 CA 机构弃用,现在前端最常用的是 SHA-256(属于 SHA-2 家族),输出 256 位摘要(64 个十六进制字符)。

SHA-256 和 MD5 一样也是哈希,也不可逆,但碰撞难度大得多,目前没有实际可行的碰撞攻击。用做密码摘要、接口参数完整性校验、文件指纹都没问题。

用 crypto-js 实现:

import sha256 from 'crypto-js/sha256'; const hash = sha256('前端数据加密').toString(); console.log(hash); // 64位hex字符串

如果项目里不想引入 crypto-js 这么大的库,现代浏览器原生提供了 Web Crypto API,性能更好,而且是浏览器内置能力:

async function sha256Hex(message) { const encoder = new TextEncoder(); const data = encoder.encode(message); 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(''); } sha256Hex('前端数据加密').then(hash => console.log(hash));

注意 Web Crypto API 的几个特点:只能在安全上下文(HTTPS 或 localhost)里使用;digest返回的是 Promise;输入输出都是ArrayBuffer。这几个特点在日常开发里往往会拦截一批人。

SHA 家族选型建议很简单:拿不准就用 SHA-256,别纠结。将来有更强的需求时 SHA-384、SHA-512 的调用方式和 SHA-256 完全一样,只是改个字符串的事。

3. AES 对称加密:适合本地存储与数据传输脱敏的主力方案

3.1 对称加密为什么快,又为什么有痛点

AES(Advanced Encryption Standard)是最常用的对称加密算法,也是现代密码学的地基之一。所谓对称,指的是加密和解密用同一个密钥。它的核心优势是快,硬件和软件实现都极优,特别适合加密大数据量。

AES 属于分组密码,把明文按固定块大小(128 位即 16 字节)分组处理。为了处理任意长度的数据,加密模式非常重要。前端实践中最常见的两个模式是 CBC 和 GCM。

CBC 模式(密码分组链接):每个明文块先和上一个密文块做异或,再用密钥加密。第一个块没有前一个密文,所以需要初始化向量 IV。CBC 的缺点是不能并行加密、密文可能被篡改但不一定能及时发现。

GCM 模式(伽罗瓦计数器模式):不仅加密,还内置完整性校验,输出“密文 + 认证标签”。GCM 比 CBC 多了一层防篡改能力,是当前更推荐的模式。Web Crypto API 原生支持 AES-GCM,但 crypto-js 比较老的版本默认只有 CBC,要特别注意。

AES 的密钥长度有 128、192、256 位三种,位数越高越难暴力破解。前端推荐直接用 AES-256。

3.2 用 crypto-js 实现 AES-CBC 加解密

crypto-js 是最流行的前端加密库,几乎零依赖,用法也直白。一个完整的 AES-CBC 加解密例子:

import CryptoJS from 'crypto-js'; // 密钥和 IV 都必须是 16 字节(128 位)字符串,比如 16 个英文字符 const key = CryptoJS.enc.Utf8.parse('0123456789abcdef'); const iv = CryptoJS.enc.Utf8.parse('abcdef9876543210'); function aesEncrypt(plainText) { const encrypted = CryptoJS.AES.encrypt(plainText, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); // 默认输出 Base64 密文 } function aesDecrypt(cipherText) { const decrypted = CryptoJS.AES.decrypt(cipherText, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return decrypted.toString(CryptoJS.enc.Utf8); } const cipherText = aesEncrypt('用户身份证号:110101199001011234'); const plainText = aesDecrypt(cipherText); console.log(cipherText); // 一串 Base64 密文 console.log(plainText); // 用户身份证号:110101199001011234

几个容易踩的坑:

  • 密钥和 IV 用Utf8.parse转换,因为AES.encrypt里直接传普通字符串会被当成密码,走的是 OpenSSL 兼容的 EVP_BytesToKey 密钥派生逻辑,和后端常见的节点加密实现对接不上。
  • 模式不确定时校验失败:后端如果用的是 AES-256-GCM,前端不能拿 CBC 硬解。
  • 解密后的toString(CryptoJS.enc.Utf8)如果结果是空字符串,大概率是密钥、IV、密文格式三者有一样不对。

3.3 用 Web Crypto API 实现 AES-GCM

如果你不想引入第三方库,现代浏览器自带的 Web Crypto API 完全能胜任,而且它支持 GCM 模式,安全性上更省心。一个完整的 AES-GCM 加解密流程要涉及密钥导入、随机 IV 生成、ArrayBuffer 转 Base64,比 crypto-js 繁琐一点,但理解之后反而更可控。

// 生成一个随机 AES-256 密钥 async function generateAesKey() { return crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); } // ArrayBuffer 转 Base64 字符串 function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; bytes.forEach(b => (binary += String.fromCharCode(b))); return btoa(binary); } // Base64 字符串转 ArrayBuffer function base64ToArrayBuffer(base64) { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; } async function aesGcmEncrypt(plainText, aesKey) { const iv = crypto.getRandomValues(new Uint8Array(12)); // GCM 推荐 12 字节 IV const encoder = new TextEncoder(); const cipherBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, aesKey, encoder.encode(plainText) ); // 需要把 IV 和密文一起发给后端,否则无法解密 return { iv: arrayBufferToBase64(iv), cipherText: arrayBufferToBase64(cipherBuffer) }; }

Web Crypto API 最迷惑人的地方在于:它不直接操作字符串,所有输入输出都是 ArrayBuffer,类型转换写起来比较啰嗦。但好处是安全默认值做得好,比如生成密钥、随机 IV 都内置了密码学安全的随机源,比自己写Math.random()靠谱多了。

有一点必须强调:GCM 模式加密后返回的认证标签已经拼在密文尾部,后端拿到后会自动校验。如果只是简单地把密文存下来不管认证结果,那 GCM 的防篡改优势就浪费了。

3.4 key 与 IV 的保管原则

AES 用起来最关键的其实不是加密代码,而是密钥管理。我见过太多项目把密钥硬编码在 JS 文件里,这样的 AES 等同于裸奔,因为攻击者只要打开前端代码就能拿到密钥,然后想解密什么就解密什么。

哪些做法是合理的?分场景说。

如果你的场景是“后端给前端下发一个临时密钥,用于特定接口的加密通信”,那密钥应该通过安全接口下发,最好带着有效期,过期作废,而不是写死在代码里。

如果你的场景是“前端要把一个数据存在 localStorage 里,防止别人直接改”,这种托管的加密密钥其实还是有可能被提取的。你能做的是提高提取和篡改的成本,比如结合浏览器指纹、对密钥做拆分混淆,但心里要清楚这依然不是铁桶。

IV 不需要保密,但绝对不能固定。每次加密都要随机生成一个 IV,并把 IV 和密文一起传给对端。因为 IV 的作用就是让同样的明文每次产生不同的密文,固定 IV 会让攻击者发现重复规律,相当于拆掉了 AES 的半条防线。

实操里我喜欢这样组织前端加密工具模块:

import CryptoJS from 'crypto-js'; // 从安全渠道获取密钥,而不是写死在仓库里 let sessionKey = ''; export function setSessionKey(key) { sessionKey = key; } export function encryptData(plainText) { const key = CryptoJS.enc.Utf8.parse(sessionKey); const iv = CryptoJS.lib.WordArray.random(16); const encrypted = CryptoJS.AES.encrypt(plainText, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return { iv: iv.toString(), data: encrypted.toString() }; }

4. RSA 非对称加密:面向敏感短消息的“专属通道”

4.1 非对称加密的核心思想

RSA 和前面所有方案的本质区别是有两把钥匙:公钥和私钥。公钥公开给所有人,私钥只有你自己持有。公钥加密的数据只能用私钥解密;反过来,私钥签名后也只能用公钥验签。这就绕开了“对称密钥怎么安全传给对方”这个难题。

前端场景里,RSA 最常见的用途是加密登录密码。前端持有公钥,把密码加密成密文,后端持有私钥,拿到密文后解密。即使密文被第三方截获,因为没有私钥,也还原不出密码。

密钥对可以通过多种方式生成。前端调试时,直接在 Node.js 环境生成:

openssl genrsa -out private_key.pem 2048 openssl rsa -in private_key.pem -pubout -out public_key.pem

4.2 用 jsencrypt 快速落地登录密码加密

前端做 RSA 加密,最省事的库是 jsencrypt,API 非常简洁,对新手友好。

import JSEncrypt from 'jsencrypt'; const publicKey = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----`; // 加密 const encryptor = new JSEncrypt(); encryptor.setPublicKey(publicKey); const encrypted = encryptor.encrypt('用户密码原文'); console.log(encrypted); // 解密(仅用于调试或特殊场景,真实项目私钥绝不能放前端) const decryptor = new JSEncrypt(); decryptor.setPrivateKey('私钥...'); console.log(decryptor.decrypt(encrypted));

jsencrypt 默认使用 PKCS#1 v1.5 填充,加密后的密文每次都不一样,这点不用担心,属于正常现象。

用 rs 库时注意公钥格式必须带-----BEGIN PUBLIC KEY-----头,不能直接把 Base64 字符串丢进去。从很多后管系统平台复制公钥时容易粘贴成裸字符串,导致encrypt结果为空或者直接返回 false。

如果项目已经用了 Web Crypto API,RSA 加密稍微繁琐一点但也能做。核心是导入公钥、选择 RSA-OAEP 算法、然后crypto.subtle.encrypt:

async function rsaEncrypt(publicKeyPem, plainText) { const pemHeader = '-----BEGIN PUBLIC KEY-----'; const pemFooter = '-----END PUBLIC KEY-----'; const pemBody = publicKeyPem.replace(pemHeader, '').replace(pemFooter, '').replace(/\n/g, ''); const binaryDer = Uint8Array.from(atob(pemBody), c => c.charCodeAt(0)); const publicKey = await crypto.subtle.importKey( 'spki', binaryDer.buffer, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt'] ); const encrypted = await crypto.subtle.encrypt( { name: 'RSA-OAEP' }, publicKey, new TextEncoder().encode(plainText) ); // 把 ArrayBuffer 转 Base64 返回 }

4.3 RSA 性能限制与混合加密思路

RSA 最大的限制是慢和短。以 2048 位密钥为例,一次能加密的明文长度只有大约 245 字节(256 字节减掉填充开销)。你要是一股脑把整个表单数据都丢给 RSA 加密,直接报错或者得到空结果。

所以真实项目里很少全量用 RSA,而是用“混合加密”方案:

  1. 前端生成一个随机 AES 密钥,用 AES 加密整块业务数据;
  2. 再用后端的 RSA 公钥加密这个 AES 密钥;
  3. 把“RSA 加密后的 AES 密钥”和“AES 密文”一起发给后端;
  4. 后端先用 RSA 私钥解出 AES 密钥,再用 AES 解密业务数据。

这样既绕开了 RSA 的长度限制,又利用了 AES 的加密速度,是前后端接口数据保密的经典架构。(注意:这里的密钥协商流程属于业务层加密设计,需要配合 HTTPS 通道一起使用,才能防止中间人整体替换数据。)

混合加密的逻辑不复杂,难在前后端要对齐格式。我的建议是:前端明确用一个对象返回两段数据,并让后端按约定好的字段名解析,IV 也是同样处理。

5. HMAC 消息认证码:不做加密,专治篡改

5.1 HMAC 与普通 Hash 的本质差别

HMAC(Hash-based Message Authentication Code)初看像哈希——也输出一段摘要,但它的输入除了消息本身,还有一个共享密钥。

HMAC结果 = Hash(密钥 + 消息 + 密钥)

这个“密钥”让 HMAC 和普通 SHA-256 有了本质区别:普通 SHA-256 谁都能算,任何人改一下消息重新算一遍哈希就能骗过完整性校验;HMAC 只有持有密钥的人才能算出正确摘要,所以它不仅验证“内容没被改”,还验证“这条消息确实是知道密钥的人发出的”。

日常开发里我最常用 HMAC 做接口签名。前端发起请求前,把请求参数按约定排序、拼接,再用 HMAC-SHA256 加上一个密钥算出签名,后端拿到请求后用同样的参数同样的密钥重算签名,不一致就拒绝请求。

5.2 API 签名场景的落地实现

用 crypto-js 实现一个标准且简洁的 HMAC-SHA256:

import HmacSHA256 from 'crypto-js/hmac-sha256'; import Hex from 'crypto-js/enc-hex'; function generateSignature(params, secretKey) { // 1. 把参数按 key 字典序排序,拼成 query string const sortedKeys = Object.keys(params).sort(); const queryString = sortedKeys .map(key => `${key}=${encodeURIComponent(params[key])}`) .join('&'); // 2. 拼接时间戳和随机数,防重放 const timestamp = Date.now(); const nonce = Math.random().toString(36).slice(2); const message = `${timestamp}&${nonce}&${queryString}`; // 3. 计算 HMAC const signature = HmacSHA256(message, secretKey).toString(Hex); return { timestamp, nonce, signature }; } // 调用示例 const result = generateSignature( { userId: 1001, type: 'query', page: 1 }, '用户专属密钥,后端下发' );

这段代码在真实项目里已经接近可用了。有几个细节值得注意:

  • 参数必须排序,保证前后端拼接顺序一致,否则同一对象算出来签名不同;
  • 拼接时encodeURIComponent用来处理特殊字符;
  • 加 timestamp 和 nonce 是为了防重放攻击。timestamp 让后端可以拒绝过期请求,nonce 让同一个请求只能被接收一次,二者缺一不可。

5.3 防篡改和防重放的分工

很多刚接触签名的同学会把“防篡改”和“防重放”混在一起,这里认真掰扯一下。

防篡改的意思是:数据在传输途中被人改了,但校验签名后能发现。实现方式就是上面提到的签名算法。

防重放的意思是:攻击者不修改数据,而是把之前抓到的合法请求原封不动再发一遍。比如转账请求,攻击者不用看内容,直接重复提交就可能导致重复扣款。只靠签名解决不了这个问题,必须检查 timestamp(时间窗)和 nonce(随机数)。

所以在设计接口签名方案时,后端一定要做两件事:

  • 检查 timestamp 与服务器当前时间差是否在允许范围内(常见是 5 分钟);
  • 检查 nonce 是否已经用过,用过就直接拒绝,并在有效期结束后清理 nonce 缓存。

我见过有的项目只做了 timestamp 校验没做 nonce 校验,结果攻击者在 5 分钟窗口内疯狂重放请求。还有的项目 nonce 用了随机数但没落库检查,等于白做。这些都是踩过的真坑。

6. 六种方式选型对照与个人实战心得

6.1 一张表理清选型逻辑

前面把六种方式都过了一遍,最后用一张表直接总结选型逻辑。这张表我平时设计接口方案时也经常参考。

方式可逆性是否需密钥典型前端场景安全强度
Base64可逆无图片展示、参数编码传输不加密
MD5不可逆无文件校验、老系统密码摘要(需加盐)弱
SHA-256不可逆无密码摘要、数据完整性校验较高
AES可逆对称密钥本地存储加密、业务数据批量加密高
RSA可逆公私钥登录密码加密、AES 密钥传输高
HMAC不可逆共享密钥接口签名、防篡改与防重放高

每次做技术选型,我习惯先问自己一个问题:这段数据需要还原吗?需要还原选可逆方案(Base64 或对称/非对称加密),不需要还原选哈希方案。再来一个维度:需要证明“谁发的”吗?需要就上 HMAC 或 RSA 签名。这两个问题问完,方案基本就出来了。

6.2 我踩过的几个实战坑

第一个坑是 crypto-js 的导入问题。老项目里常见import CryptoJS from 'crypto-js'全量引入,打包体积能到上百 KB。按需引入可以大幅减小包体:

import AES from 'crypto-js/aes'; import encUtf8 from 'crypto-js/enc-utf8'; import modeCBC from 'crypto-js/mode-cbc'; import padPkcs7 from 'crypto-js/pad-pkcs7';

第二个坑是加密后的字符串在 URL 里被截断。AES 密文是 Base64 字符串,里面可能带+、/、=这三个字符。把这些字符串直接拼进 URL,后端收到的密文可能已经被改写,解密必然失败。解决办法是传给后端前做 URL 安全处理:

function urlSafeBase64(base64Str) { return base64Str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); }

第三个坑是 Web Crypto API 必须在 HTTPS 环境跑。本地file://协议或局域网 IP 直接访问,crypto.subtle会是 undefined。调试时要么用 localhost 启动本地服务,要么临时用 HTTP 配合 localhost 白名单场景,线上必须走 HTTPS。

第四个坑是 RSA 加密空数据或超长数据。JSEncrypt的encrypt对空字符串返回 false,超长直接失败。所以前端提交前一定要先判断字段是否为空,长度超限就拆分或改混合加密。

6.3 最后分享一个调试小技巧

前端加密最痛苦的事情是“前端加密了,后端解不开”。排查这种问题,最快的办法不是反复改代码,而是先确定“哪一段链路出了问题”。

我的固定排查顺序是:

  • 先确认算法名对齐:CBC 还是 GCM,OAEP 还是 PKCS#1 v1.5,就这一个字符串不对,全部白搭;
  • 再确认密钥格式:十六进制还是 Base64,UTF-8 还是 WordArray,格式不一致是最大元凶;
  • 然后确认填充方式:PKCS7、NoPadding、OAEP 的摘要函数是否一致;
  • 最后才怀疑数据内容:编码格式、特殊字符、长度限制。

排查的时候我习惯在前端把加密前、加密后的值都console.log出来,另存一份传给后端让后端输出来比对,两边一对照问题立刻浮出水面。这个习惯帮我省过很多次和同事来回扯皮的功夫。

前端数据加密这件事,说到底是一门“正确的姿势 + 清晰的边界”的手艺。算法本身不需要你重新发明,但什么时候用什么、密钥怎么藏、签名怎么设计,才是真正区分经验的地方。把这六种方式吃透,再遇到登录加密、接口签名、数据脱敏、本地缓存加密这些需求,你就不会慌,也不会再靠网上搜一段代码就往上贴了。

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

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

立即咨询