
在前端开发中,用户密码传输、本地存储敏感数据、接口请求加密等场景,都离不开可靠的加密方案。AES(高级加密标准)作为对称加密领域的 “安全标杆”,凭借跨平台兼容性和高安全性,成为 JavaScript 开发者保障数据安全的核心工具。本文从基础原理出发,拆解关键参数,再通过浏览器与 Node.js 双场景实战,教你实现符合工业级标准的 AES 加密。
一、AES 加密算法基础认知
1. 什么是 AES 加密算法
AES 是美国国家标准与技术研究院(NIST)于 2001 年发布的对称加密标准,用以替代安全性不足的 DES 算法。其核心特征是加密与解密使用同一密钥,通过固定 128 位(16 字节)分组处理数据,仅需持有正确密钥即可还原明文,是目前 Web 领域应用最广泛的加密算法之一。
2. AES 的核心优势
- 安全强度可靠:支持 128 位、192 位、256 位密钥长度,128 位密钥已能抵御当前主流破解手段,256 位密钥更可满足金融级安全需求。
- 前端适配性强:无论是浏览器环境还是 Node.js 服务端,都有成熟的加密库支持,无需复杂底层实现。
- 性能损耗低:算法逻辑精简,在移动端浏览器中也能高效运行,不会明显影响页面性能。
3. 前端典型应用场景
- 用户登录密码加密(避免明文传输);
- 本地存储敏感数据(如 Token、用户配置);
- 前后端接口请求体 / 响应体加密;
- 前端生成的隐私文件(如导出 Excel)加密。
二、AES 核心参数深度拆解
AES 加密的安全性不仅依赖密钥,iv(初始向量)、mode(加密模式)、padding(填充方式)三大参数的正确配置,直接决定加密效果,三者缺一不可。
1. 初始向量(IV):加密的 “随机干扰因子”
(1)定义与核心作用
IV 是加密前加入的随机字节串,本质是打破 “一样明文 + 一样密钥 = 一样密文” 的固定关系。即使两次加密的明文和密钥完全一致,只要 IV 不同,最终密文就会截然不同,从而抵御字典攻击和彩虹表破解。
(2)关键规范与前端误区
- 长度规则:AES 分组长度固定为 16 字节,但 GCM 模式推荐 12 字节 IV(符合 RFC 5116 标准,兼顾安全与性能),CBC 模式需严格使用 16 字节 IV。
- 生成要求:必须通过加密安全的随机数生成器生成,浏览器端推荐window.crypto.getRandomValues(),Node.js 端推荐crypto.randomBytes(),严禁使用Math.random()(伪随机易被预测)。
- 传输原则:IV 无需保密,可随密文一同传输(如拼接在密文前),解密时需使用与加密完全一致的 IV。
- 前端常见错误:将 IV 硬编码在代码中、同一密钥重复使用 IV、用用户输入(如手机号)作为 IV。
2. 加密模式(Mode):数据加密的 “执行逻辑”
加密模式定义了 AES 分组的处理方式,不同模式在安全性、性能和适用场景上差异显著,前端开发需根据场景选择。
|
加密模式 |
核心特点 |
安全性 |
适用场景 |
JavaScript 实现注意事项 |
|
ECB(电子密码本) |
明文分组独立加密,无 IV 需求 |
极低(一样明文分组密文一样,易暴露规律) |
仅测试场景,严禁生产使用 |
主流加密库默认禁用或告警 |
|
CBC(密码分组链接) |
前一分组密文参与后一分组加密,需 IV |
中高 |
兼容性要求高的旧系统、简单数据加密 |
需搭配填充方式,浏览器端需注意跨域加密互通 |
|
GCM(伽罗瓦 / 计数器) |
融合加密与认证,生成认证标签 |
极高(加密 + 完整性校验二合一) |
密码传输、接口加密、高安全需求场景 |
无需填充,支持并行加密,推荐优先使用 |
|
CTR(计数器) |
通过计数器生成伪随机流与明文异或,需 IV |
中高 |
实时通信、大文件分段加密 |
无需填充,支持并行处理,但需额外校验完整性 |
核心推荐
前端生产环境首选 GCM 模式—— 其独有的认证标签(Tag)能自动验证数据完整性,一旦密文被篡改,解密时会直接抛出异常,从根本上防止数据篡改攻击。若需兼容仅支持 CBC 模式的旧后端,需额外通过 HMAC 算法验证数据完整性。
3. 填充方式(Padding):分组加密的 “补全工具”
AES 是严格的分组加密算法,要求明文长度必须是 16 字节的整数倍。当明文长度不足时,需通过填充方式补全;即使长度刚好达标,仍需填充(避免解密时无法区分真实数据与填充内容)。
|
填充方式 |
填充规则 |
适用场景 |
JavaScript 库支持情况 |
|
PKCS7Padding |
填充字节值等于填充长度(如缺 3 字节则填充 3 个 0x03) |
绝大多数场景,兼容性最佳 |
crypto-js、node-crypto 均原生支持 |
|
PKCS5Padding |
与 PKCS7 逻辑一致,仅分组长度固定为 8 字节(AES 场景下等同于 PKCS7) |
需兼容旧系统的场景 |
多数库通过 PKCS7 实现,名称统一为 PKCS7 |
|
NoPadding |
不填充,要求明文长度必须是 16 字节整数倍 |
自定义填充逻辑、固定长度数据加密 |
GCM/CTR 模式默认无需填充,CBC 模式需手动处理 |
核心推荐
前端优先选择PKCS7Padding(主流库均支持),GCM/CTR 等流加密模式无需手动配置填充(库会自动处理),CBC 模式必须显式指定填充方式。
三、AES 算法在 JavaScript 中的最佳实践
JavaScript 实现 AES 加密,需区分浏览器端与 Node.js 端,主流依赖库为crypto-js(浏览器端常用)和 Node.js 原生crypto模块(服务端推荐)。以下提供双场景实战代码,并规避前端安全风险。
1. 库选择与环境准备
(1)库特性对比
|
特性 |
crypto-js(浏览器端) |
Node.js crypto(服务端) |
|
引入方式 |
脚本标签或 npm 安装(需注意体积) |
Node.js 原生模块,无需额外安装 |
|
API 设计 |
高层封装,调用简单 |
底层接口,需手动处理参数 |
|
安全性 |
需注意版本更新,避免漏洞 |
随 Node.js 版本更新,安全性有保障 |
|
适用场景 |
浏览器端加密、轻量前端项目 |
Node.js 服务端加密、前后端加密互通 |
(2)环境准备
- 浏览器端(crypto-js):
|
<!– 方式1:CDN引入(推荐,减少打包体积) –> <script src=”https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js”></script> <!– 方式2:npm安装(适用于Vue/React项目) –> <!– npm install crypto-js@4.2.0 –> <!– 引入:import CryptoJS from 'crypto-js' –> |
- Node.js 端(原生 crypto):
无需额外安装,直接引入原生模块:
|
const crypto = require('crypto'); |
2. 浏览器端最佳实践:GCM 模式加密(推荐)
GCM 模式兼具加密与认证功能,是浏览器端高安全场景的首选,以下代码包含密钥生成、加密、解密全流程,并处理 IV 与标签的传输。
|
// 浏览器端AES-GCM加密工具(基于crypto-js) class AESGcmBrowserUtil { // 密钥长度:16(128位)/24(192位)/32(256位),推荐32位(安全性更高) static KEY_SIZE = 32; // GCM模式推荐12字节IV,16字节认证标签 static IV_SIZE = 12; static TAG_SIZE = 16; /** * 生成AES密钥(Base64编码) * @returns {string} Base64编码的密钥 */ static generateKey() { // 使用浏览器安全随机数生成密钥 const keyBytes = new Uint8Array(this.KEY_SIZE); window.crypto.getRandomValues(keyBytes); // 转为Base64编码(便于存储和传输) return btoa(String.fromCharCode(…keyBytes)); } /** * 加密方法 * @param {string} plainText 待加密明文 * @param {string} keyBase64 Base64编码的密钥 * @param {Uint8Array} [aad] 附加认证数据(可选,如设备ID、时间戳,增强安全性) * @returns {string} 加密结果(IV+密文+标签的Base64编码) */ static encrypt(plainText, keyBase64, aad = new Uint8Array(0)) { try { // 1. 解析密钥(Base64转Uint8Array) const keyBytes = new Uint8Array(atob(keyBase64).split('').map(c => c.charCodeAt(0))); // 2. 生成随机IV const iv = new Uint8Array(this.IV_SIZE); window.crypto.getRandomValues(iv); // 3. 初始化加密器(GCM模式) const encryptPromise = window.crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv, additionalData: aad, // 附加认证数据 tagLength: this.TAG_SIZE * 8 // 标签长度(单位:比特) }, window.crypto.subtle.importKey( 'raw', // 密钥格式 keyBytes, 'AES-GCM', // 算法名称 false, // 是否可导出 ['encrypt'] // 密钥用途 ), new TextEncoder().encode(plainText) // 明文转Uint8Array ); // 4. 执行加密并处理结果 return encryptPromise.then(cipherBuffer => { const cipherBytes = new Uint8Array(cipherBuffer); // 拼接IV+密文+标签(标签包含在cipherBytes末尾) const result = new Uint8Array(this.IV_SIZE + cipherBytes.length); result.set(iv, 0); result.set(cipherBytes, this.IV_SIZE); // 转为Base64编码(便于传输) return btoa(String.fromCharCode(…result)); }); } catch (error) { throw new Error(`加密失败:${error.message}`); } } /** * 解密方法 * @param {string} cipherTextBase64 加密结果的Base64编码 * @param {string} keyBase64 Base64编码的密钥 * @param {Uint8Array} [aad] 与加密时一致的附加认证数据 * @returns {Promise<string>} 解密后的明文 */ static decrypt(cipherTextBase64, keyBase64, aad = new Uint8Array(0)) { try { // 1. 解析加密结果(Base64转Uint8Array) const resultBytes = new Uint8Array(atob(cipherTextBase64).split('').map(c => c.charCodeAt(0))); // 2. 解析密钥 const keyBytes = new Uint8Array(atob(keyBase64).split('').map(c => c.charCodeAt(0))); // 3. 提取IV(前12字节)和密文+标签(剩余部分) const iv = resultBytes.subarray(0, this.IV_SIZE); const cipherBytes = resultBytes.subarray(this.IV_SIZE); // 4. 初始化解密器 const decryptPromise = window.crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, additionalData: aad, tagLength: this.TAG_SIZE * 8 }, window.crypto.subtle.importKey( 'raw', keyBytes, 'AES-GCM', false, ['decrypt'] ), cipherBytes ); // 5. 执行解密并返回明文 return decryptPromise.then(plainBuffer => { return new TextDecoder().decode(plainBuffer); }); } catch (error) { // 认证标签不匹配或密钥错误,统一返回通用错误(避免泄露细节) throw new Error('解密失败:数据可能被篡改或密钥错误'); } } } // 浏览器端测试示例 async function browserTest() { // 生成密钥(实际项目中密钥需从后端获取,避免前端生成) const key = AESGcmBrowserUtil.generateKey(); console.log('生成密钥:', key); const plainText = '浏览器端AES-GCM加密测试:userPassword123'; // 附加认证数据(可加入用户ID、时间戳,增强安全性) const aad = new TextEncoder().encode('userId:123;timestamp:1728614400'); // 加密 const cipherText = await AESGcmBrowserUtil.encrypt(plainText, key, aad); console.log('加密结果:', cipherText); // 解密 const decryptedText = await AESGcmBrowserUtil.decrypt(cipherText, key, aad); console.log('解密结果:', decryptedText); } // 调用测试 browserTest(); |
3. Node.js 端最佳实践:GCM 模式加密(服务端推荐)
Node.js 端推荐使用原生crypto模块(无需额外安装,安全性高),以下代码实现与浏览器端互通的 AES-GCM 加密。
|
// Node.js端AES-GCM加密工具(基于原生crypto模块) const crypto = require('crypto'); class AESGcmNodeUtil { static KEY_SIZE = 32; // 256位密钥 static IV_SIZE = 12; // GCM模式12字节IV static TAG_SIZE = 16; // 16字节认证标签 /** * 生成AES密钥 * @returns {string} Base64编码的密钥 */ static generateKey() { const key = crypto.randomBytes(this.KEY_SIZE); return key.toString('base64'); } /** * 加密方法 * @param {string} plainText 待加密明文 * @param {string} keyBase64 Base64编码的密钥 * @param {Buffer} [aad] 附加认证数据(可选) * @returns {string} 加密结果(IV+密文+标签的Base64编码) */ static encrypt(plainText, keyBase64, aad = Buffer.alloc(0)) { try { // 解析密钥 const key = Buffer.from(keyBase64, 'base64'); // 生成随机IV const iv = crypto.randomBytes(this.IV_SIZE); // 初始化加密器 const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); // 添加附加认证数据(若有) if (aad.length > 0) { cipher.setAAD(aad); } // 执行加密 let cipherText = cipher.update(plainText, 'utf8', 'hex'); cipherText += cipher.final('hex'); // 获取认证标签(cipher.getAuthTag()需在final后调用) const tag = cipher.getAuthTag().toString('hex'); // 拼接IV+密文+标签(均为hex格式),再转Base64 const resultHex = iv.toString('hex') + cipherText + tag; return Buffer.from(resultHex, 'hex').toString('base64'); } catch (error) { throw new Error(`加密失败:${error.message}`); } } /** * 解密方法 * @param {string} cipherTextBase64 加密结果的Base64编码 * @param {string} keyBase64 Base64编码的密钥 * @param {Buffer} [aad] 与加密时一致的附加认证数据 * @returns {string} 解密后的明文 */ static decrypt(cipherTextBase64, keyBase64, aad = Buffer.alloc(0)) { try { // 解析加密结果 const resultHex = Buffer.from(cipherTextBase64, 'base64</doubaocanvas> |


