本文介绍如何使用 Web Crypto API 和 sm-crypto 库在浏览器端实现常用加密算法,包括 MD5/SHA 哈希、AES 对称加密、RSA 非对称加密、国密 SM2/SM3/SM4,以及 JWT 解码。含完整代码示例。
前端加密的典型场景
- 密码哈希:用户密码不能明文传输,前端先 MD5/SHA 哈希再发到后端
- 数据加密传输:敏感数据用 AES 加密后再传输,即使 HTTPS 被中间人攻击也无法解密
- RSA 密钥对生成:在浏览器端生成公私钥对,私钥不离开设备
- 国密合规:金融、政务场景要求使用国密算法(SM2/SM3/SM4)
- JWT 调试:前端需要解码 JWT Token 查看载荷内容
Web Crypto API 简介
Web Crypto API 是浏览器原生提供的加密接口,基于 window.crypto.subtle。
优势:
- 浏览器原生支持,无需第三方库
- 底层用 C/C++ 实现,性能远高于 JS 库
- 密钥材料不可导出(除非显式标记
extractable: true)
限制:
- 不支持 MD5(已被认为不安全)
- 不支持国密算法
- API 是异步的,返回 Promise
所以我们需要补充:
- MD5 用第三方库(如
crypto-js) - 国密用
sm-crypto库
实战代码
1. SHA 哈希(Web Crypto API 原生)
// SHA-256 哈希
async function sha256(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('');
}
// 支持 SHA-1, SHA-256, SHA-384, SHA-512
async function sha(message, algorithm = 'SHA-256') {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await crypto.subtle.digest(algorithm, data);
return bufferToHex(hashBuffer);
}
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
2. HMAC 签名
async function hmacSign(message, key, algorithm = 'SHA-256') {
const encoder = new TextEncoder();
// 导入密钥
const cryptoKey = await crypto.subtle.importKey(
'raw',
encoder.encode(key),
{ name: 'HMAC', hash: algorithm },
false,
['sign']
);
// 签名
const signature = await crypto.subtle.sign(
'HMAC',
cryptoKey,
encoder.encode(message)
);
return bufferToHex(signature);
}
3. AES 加密解密
// AES-CBC 加密
async function aesEncrypt(plaintext, password) {
const encoder = new TextEncoder();
// 从密码派生密钥(PBKDF2)
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(16));
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-CBC', length: 256 },
false,
['encrypt', 'decrypt']
);
// 加密
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-CBC', iv: iv },
key,
encoder.encode(plaintext)
);
// 返回 salt + iv + ciphertext(base64)
const combined = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
combined.set(salt, 0);
combined.set(iv, salt.length);
combined.set(new Uint8Array(ciphertext), salt.length + iv.length);
return btoa(String.fromCharCode(...combined));
}
// AES-CBC 解密
async function aesDecrypt(ciphertextBase64, password) {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// 解析 salt + iv + ciphertext
const combined = Uint8Array.from(atob(ciphertextBase64), c => c.charCodeAt(0));
const salt = combined.slice(0, 16);
const iv = combined.slice(16, 32);
const ciphertext = combined.slice(32);
// 派生密钥
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-CBC', length: 256 },
false,
['decrypt']
);
// 解密
const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-CBC', iv: iv },
key,
ciphertext
);
return decoder.decode(plaintext);
}
4. RSA 密钥对生成与加解密
// 生成 RSA 密钥对
async function generateRSAKeyPair(bits = 2048) {
const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: bits,
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: 'SHA-256'
},
true, // extractable
['encrypt', 'decrypt']
);
// 导出为 PEM 格式
const publicKey = await crypto.subtle.exportKey('spki', keyPair.publicKey);
const privateKey = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey);
return {
publicKey: arrayBufferToPEM(publicKey, 'PUBLIC KEY'),
privateKey: arrayBufferToPEM(privateKey, 'PRIVATE KEY')
};
}
function arrayBufferToPEM(buffer, type) {
const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
const lines = base64.match(/.{1,64}/g).join('\n');
return `-----BEGIN ${type}-----\n${lines}\n-----END ${type}-----`;
}
// RSA 加密(用公钥)
async function rsaEncrypt(plaintext, publicKeyPEM) {
const publicKey = await importPublicKey(publicKeyPEM);
const encoder = new TextEncoder();
const ciphertext = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
publicKey,
encoder.encode(plaintext)
);
return btoa(String.fromCharCode(...new Uint8Array(ciphertext)));
}
5. 国密 SM2/SM3/SM4(sm-crypto 库)
Web Crypto API 不支持国密算法,需要用 sm-crypto 库:
npm install sm-crypto
import { sm2, sm3, sm4 } from 'sm-crypto';
// SM3 哈希(类似 SHA-256 的国密版本)
const hash = sm3('Hello World');
// SM4 对称加密(类似 AES 的国密版本)
const key = '0123456789abcdef0123456789abcdef'; // 128bit hex
const ciphertext = sm4.encrypt('Hello World', key);
// SM2 非对称加密(类似 RSA 的国密版本)
const keypair = sm2.generateKeyPairHex();
// keypair.publicKey 公钥
// keypair.privateKey 私钥
const encrypted = sm2.doEncrypt('Hello World', keypair.publicKey);
const decrypted = sm2.doDecrypt(encrypted, keypair.privateKey);
6. JWT 解码
JWT 由三部分组成:header.payload.signature,用 . 分隔。前两部分是 Base64URL 编码的 JSON:
function decodeJWT(token) {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}
// Base64URL 解码
function base64UrlDecode(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
return atob(str);
}
const header = JSON.parse(base64UrlDecode(parts[0]));
const payload = JSON.parse(base64UrlDecode(parts[1]));
return {
header,
payload,
signature: parts[2],
// 检查是否过期
isExpired: payload.exp ? (Date.now() / 1000 > payload.exp) : false
};
}
安全注意事项
-
MD5 不安全:MD5 已被证明存在碰撞攻击,不要用于安全场景。密码哈希用 SHA-256 或更强的算法。
-
AES 密钥派生:不要直接用用户密码作为 AES 密钥,用 PBKDF2 派生,迭代次数至少 100000。
-
RSA 密钥长度:至少 2048 位,推荐 4096 位。1024 位已被认为不安全。
-
前端加密不能替代 HTTPS:前端加密是额外安全层,不是 HTTPS 的替代品。中间人可以替换页面中的 JS 代码,绕过前端加密。
-
国密算法使用场景:SM2 替代 RSA,SM3 替代 SHA-256,SM4 替代 AES。金融、政务等合规场景必须使用国密。
在线体验
以下加密工具全部纯浏览器端实现,不上传服务器:
- 加密解密工具集(MD5/SHA/AES/RSA/国密SM2/SM3/SM4/JWT):https://www.aixwriter.cn/tools/crypto-tools
支持 RSA 密钥对生成并下载 PEM 文件,方便配合后端使用。
总结
| 算法 | 类型 | 实现方式 | 适用场景 |
|---|---|---|---|
| SHA-1/256/384/512 | 哈希 | Web Crypto API 原生 | 数据完整性校验、密码哈希 |
| HMAC | 签名 | Web Crypto API 原生 | API 请求签名 |
| AES-CBC/GCM | 对称加密 | Web Crypto API 原生 | 数据加密传输 |
| RSA-OAEP | 非对称加密 | Web Crypto API 原生 | 密钥交换、数字签名 |
| SM2 | 非对称加密 | sm-crypto 库 | 国密合规场景 |
| SM3 | 哈希 | sm-crypto 库 | 国密合规场景 |
| SM4 | 对称加密 | sm-crypto 库 | 国密合规场景 |
| JWT | Token 解码 | 原生 JS | 身份认证调试 |
Web Crypto API 覆盖了大部分加密需求,国密场景补充 sm-crypto 库即可。两者结合,前端加密能力已经很完善了。
本文中的加密工具由 AIWriter 提供,更多免费在线工具请访问 工具合集。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/bashendixie5/article/details/162612506




