Web Crypto API 前端加密实战:从理论到生产环境

3892 字
19 分钟
Web Crypto API 前端加密实战:从理论到生产环境

为什么前端需要加密?#

很多开发者认为「前端不安全,加密没意义」——这个观点只对了一半。确实,前端代码对用户完全透明,你无法在前端隐藏密钥。但前端加密的核心价值不在于「防用户」,而在于:

  1. 传输层加密:即使 HTTPS 被中间人攻击(公共 WiFi、企业代理),客户端加密的数据仍然安全
  2. 零知识架构:服务端只存储密文,永远不知道用户的明文数据(如密码管理器、加密笔记)
  3. 数据完整性:通过签名验证数据未被篡改
  4. 合规要求:GDPR、HIPAA 等法规要求敏感数据在客户端加密

关键原则:前端加密保护的是「数据在传输和存储时的机密性」,而不是「代码的机密性」。

认识 Web Crypto API#

Web Crypto API(window.crypto.subtle)是浏览器原生提供的密码学接口,无需任何第三方库。它基于 C++ 实现,比 JavaScript 实现的加密库(如 crypto-js)快 5-20 倍,且经过 FIPS 140-2 认证。

支持的算法一览#

算法用途浏览器支持推荐度
AES-GCM对称加密✅ 全支持⭐⭐⭐⭐⭐
AES-CBC对称加密(旧)✅ 全支持⭐⭐⭐
RSA-OAEP非对称加密✅ 全支持⭐⭐⭐⭐
RSA-PSS数字签名✅ 全支持⭐⭐⭐⭐
ECDSA椭圆曲线签名✅ 全支持⭐⭐⭐⭐⭐
ECDH密钥交换✅ 全支持⭐⭐⭐⭐
HMAC消息认证✅ 全支持⭐⭐⭐⭐
PBKDF2密钥派生✅ 全支持⭐⭐⭐⭐
Argon2密钥派生(更强)❌ 需 WASM⭐⭐⭐

⚠️ 重要限制:Web Crypto API 只能在 安全上下文(HTTPS 或 localhost)中使用。HTTP 环境下 crypto.subtleundefined

一、AES-GCM 对称加密:最快的选择#

AES-GCM(Galois/Counter Mode)是目前最推荐的对称加密算法。它同时提供加密完整性校验,一个操作完成两件事。

基础用法#

/**
* 生成 AES-GCM 密钥
* @param {number} length - 密钥长度:128 | 256
*/
async function generateAESKey(length = 256) {
return await crypto.subtle.generateKey(
{ name: 'AES-GCM', length },
true, // 可导出(用于存储或传输)
['encrypt', 'decrypt']
);
}
/**
* AES-GCM 加密
* @param {CryptoKey} key - 加密密钥
* @param {string} plaintext - 明文
* @returns {Promise<{iv: ArrayBuffer, ciphertext: ArrayBuffer}>}
*/
async function encryptAES(key, plaintext) {
const encoder = new TextEncoder();
const data = encoder.encode(plaintext);
// IV(初始化向量)必须随机且唯一!
// 重用 IV + 同一密钥 = 灾难性安全漏洞
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
data
);
return { iv, ciphertext };
}
/**
* AES-GCM 解密
*/
async function decryptAES(key, iv, ciphertext) {
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
const decoder = new TextDecoder();
return decoder.decode(decrypted);
}
// === 使用示例 ===
const key = await generateAESKey(256);
const { iv, ciphertext } = await encryptAES(key, '敏感数据:用户密码123');
const plaintext = await decryptAES(key, iv, ciphertext);
console.log(plaintext); // '敏感数据:用户密码123'

实战:加密数据的序列化存储#

ArrayBuffer 不能直接存入 localStorage 或 JSON。你需要把它转成可序列化的格式:

/**
* 将 ArrayBuffer 转为 Base64(适合 JSON/存储)
*/
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
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 encryptAndStore(key, plaintext, storageKey) {
const { iv, ciphertext } = await encryptAES(key, plaintext);
const payload = {
iv: arrayBufferToBase64(iv),
data: arrayBufferToBase64(ciphertext),
// 附加元数据
alg: 'AES-GCM',
ts: Date.now()
};
localStorage.setItem(storageKey, JSON.stringify(payload));
}
/**
* 读取并解密
*/
async function loadAndDecrypt(key, storageKey) {
const raw = localStorage.getItem(storageKey);
if (!raw) return null;
const { iv, data } = JSON.parse(raw);
return await decryptAES(
key,
base64ToArrayBuffer(iv),
base64ToArrayBuffer(data)
);
}

⚠️ AES-GCM 的常见坑#

// ❌ 错误:固定 IV(每个明文都用同一个 IV)
const FIXED_IV = new Uint8Array(12); // 全 0 的 IV
await crypto.subtle.encrypt({ name: 'AES-GCM', iv: FIXED_IV }, key, data);
// 攻击者可以通过对比密文推断明文是否相同!
// ❌ 错误:IV 太短
const iv = crypto.getRandomValues(new Uint8Array(8)); // 应该用 12 字节
// GCM 模式推荐 12 字节 IV,其他长度会触发额外的 GHASH 计算,降低性能
// ✅ 正确:每次加密都生成新 IV
const iv = crypto.getRandomValues(new Uint8Array(12));

二、RSA-OAEP + ECDSA:非对称加密与签名#

非对称加密解决了一个核心问题:如何在不安全的信道上安全地交换密钥?

RSA-OAEP 加密/解密#

/**
* 生成 RSA-OAEP 密钥对
*/
async function generateRSAKeyPair(modulusLength = 2048) {
return await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength,
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: 'SHA-256'
},
true,
['encrypt', 'decrypt']
);
}
/**
* RSA 加密(用公钥)
*/
async function rsaEncrypt(publicKey, plaintext) {
const encoder = new TextEncoder();
return await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
publicKey,
encoder.encode(plaintext)
);
}
/**
* RSA 解密(用私钥)
*/
async function rsaDecrypt(privateKey, ciphertext) {
const decrypted = await crypto.subtle.decrypt(
{ name: 'RSA-OAEP' },
privateKey,
ciphertext
);
return new TextDecoder().decode(decrypted);
}

RSA 的硬性限制:不能加密大数据#

RSA 能加密的数据大小受密钥长度限制:

RSA-2048 + SHA-256: 最大 190 字节
RSA-4096 + SHA-256: 最大 502 字节

这不是 bug,是数学限制。 RSA 是对小块数据(如 AES 密钥)操作的。加密大数据的正确做法是混合加密(Hybrid Encryption)。

混合加密:RSA + AES 的最佳实践#

/**
* 混合加密方案
* 1. 生成随机 AES 密钥
* 2. 用 AES-GCM 加密大数据
* 3. 用 RSA 加密 AES 密钥
* 4. 传输:{ encryptedData, encryptedKey, iv }
*/
async function hybridEncrypt(publicKey, largePlaintext) {
// 步骤 1:生成临时 AES 密钥
const aesKey = await generateAESKey(256);
// 步骤 2:AES 加密实际数据
const { iv, ciphertext } = await encryptAES(aesKey, largePlaintext);
// 步骤 3:导出 AES 密钥并用 RSA 加密
const rawAesKey = await crypto.subtle.exportKey('raw', aesKey);
const encryptedKey = await rsaEncrypt(publicKey,
new TextDecoder().decode(rawAesKey));
return {
encryptedData: arrayBufferToBase64(ciphertext),
encryptedKey: arrayBufferToBase64(encryptedKey),
iv: arrayBufferToBase64(iv)
};
}
/**
* 混合解密
*/
async function hybridDecrypt(privateKey, payload) {
// 步骤 1:RSA 解密 AES 密钥
const encryptedKey = base64ToArrayBuffer(payload.encryptedKey);
const decryptedKeyRaw = await rsaDecrypt(privateKey, encryptedKey);
// 步骤 2:导入 AES 密钥
const aesKey = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(decryptedKeyRaw),
{ name: 'AES-GCM', length: 256 },
true,
['decrypt']
);
// 步骤 3:AES 解密数据
return await decryptAES(
aesKey,
base64ToArrayBuffer(payload.iv),
base64ToArrayBuffer(payload.encryptedData)
);
}

ECDSA 数字签名#

/**
* 生成 ECDSA P-256 密钥对
*/
async function generateECDSAKeyPair() {
return await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign', 'verify']
);
}
/**
* 签名数据
*/
async function signData(privateKey, data) {
const encoder = new TextEncoder();
return await crypto.subtle.sign(
{ name: 'ECDSA', hash: 'SHA-256' },
privateKey,
encoder.encode(data)
);
}
/**
* 验证签名
*/
async function verifySignature(publicKey, data, signature) {
const encoder = new TextEncoder();
return await crypto.subtle.verify(
{ name: 'ECDSA', hash: 'SHA-256' },
publicKey,
signature,
encoder.encode(data)
);
}
// === 使用示例 ===
const { publicKey, privateKey } = await generateECDSAKeyPair();
const data = '转账金额:10000元,收款人:张三';
const signature = await signData(privateKey, data);
const isValid = await verifySignature(publicKey, data, signature);
console.log('签名有效:', isValid); // true
// 篡改数据后验证失败
const tampered = '转账金额:999999元,收款人:李四';
const isTampered = await verifySignature(publicKey, tampered, signature);
console.log('篡改后验证:', isTampered); // false

三、PBKDF2:从密码到加密密钥#

用户输入的密码通常不够随机、长度不够,不能直接用作加密密钥。PBKDF2(Password-Based Key Derivation Function 2)通过迭代哈希将弱密码转化为强密钥。

/**
* 从密码派生加密密钥
* @param {string} password - 用户密码
* @param {Uint8Array} salt - 随机盐值(至少 16 字节)
* @param {number} iterations - 迭代次数(OWASP 2023 推荐至少 600,000)
*/
async function deriveKeyFromPassword(password, salt, iterations = 600000) {
// 步骤 1:将密码导入为原始密钥材料
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
'PBKDF2',
false,
['deriveKey']
);
// 步骤 2:派生 AES-256 密钥
return await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false, // 密钥不可导出(更安全)
['encrypt', 'decrypt']
);
}
/**
* 生成随机盐值
*/
function generateSalt() {
return crypto.getRandomValues(new Uint8Array(16));
}
// === 完整流程:用密码加密/解密 ===
async function passwordBasedEncrypt(password, plaintext) {
const salt = generateSalt();
const key = await deriveKeyFromPassword(password, salt);
const { iv, ciphertext } = await encryptAES(key, plaintext);
return {
salt: arrayBufferToBase64(salt),
iv: arrayBufferToBase64(iv),
data: arrayBufferToBase64(ciphertext),
iterations: 600000
};
}
async function passwordBasedDecrypt(password, payload) {
const salt = base64ToArrayBuffer(payload.salt);
const key = await deriveKeyFromPassword(password, salt, payload.iterations);
return await decryptAES(
key,
base64ToArrayBuffer(payload.iv),
base64ToArrayBuffer(payload.data)
);
}

PBKDF2 迭代次数怎么选?#

迭代次数单次耗时(2024 年 CPU)安全等级建议场景
10,000~5ms⚠️ 过低不推荐
100,000~50ms⭐⭐⭐最低标准
600,000~300ms⭐⭐⭐⭐OWASP 2023 推荐
1,000,000~500ms⭐⭐⭐⭐⭐高安全场景

迭代次数越高越安全,但用户体验越差。 600,000 次迭代在移动端可能需要 1-2 秒。可以在用户输入密码时显示加载动画。

四、Python 后端验证:跨语言互操作#

前端加密的数据,后端需要能解密和验证。Python 的 cryptography 库与 Web Crypto API 完全兼容。

Python 解密 AES-GCM#

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
import json
def decrypt_aes_gcm(key_bytes: bytes, iv: bytes, ciphertext: bytes) -> str:
"""
用 Python 解密 Web Crypto API 加密的数据
AES-GCM 的 ciphertext 已包含 16 字节的认证标签(tag)
"""
aesgcm = AESGCM(key_bytes)
# decrypt() 会自动验证 tag,篡改数据会抛出 InvalidTag
plaintext = aesgcm.decrypt(iv, ciphertext, None)
return plaintext.decode('utf-8')
# === 从前端接收的 payload 解密 ===
def decrypt_from_frontend(key_bytes: bytes, payload: dict) -> str:
iv = base64.b64decode(payload['iv'])
ciphertext = base64.b64decode(payload['data'])
return decrypt_aes_gcm(key_bytes, iv, ciphertext)
# === 测试 ===
# key_bytes 应该是 32 字节(AES-256)
# 这个 key 需要通过安全方式从前端传递(如 RSA 加密的混合加密方案)
key = b'\x00' * 32 # 示例,实际应从安全通道获取
payload = {
'iv': 'AAAAAAAAAAAAAAAAAAAAAA==', # 12 字节 IV 的 base64
'data': '...' # 密文 + tag
}
# plaintext = decrypt_from_frontend(key, payload)

Python 验证 ECDSA 签名#

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
import base64
def verify_ecdsa_signature(
public_key_pem: str,
data: str,
signature_b64: str
) -> bool:
"""
验证前端 ECDSA 签名
"""
# 导入公钥(SPKI 格式)
public_key = serialization.load_der_public_key(
base64.b64decode(public_key_pem)
)
signature = base64.b64decode(signature_b64)
try:
public_key.verify(
signature,
data.encode('utf-8'),
ec.ECDSA(hashes.SHA256())
)
return True
except Exception:
return False
# === 前端导出公钥供后端验证 ===
// 前端代码:导出公钥为 SPKI 格式
async function exportPublicKeyDER(publicKey) {
const der = await crypto.subtle.exportKey('spki', publicKey);
return btoa(String.fromCharCode(...new Uint8Array(der)));
}

Python 密钥派生(PBKDF2-HMAC-SHA256)#

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
import base64
def derive_aes_key(password: str, salt_b64: str, iterations: int = 600000) -> bytes:
"""
与前端 PBKDF2 完全兼容的密钥派生
"""
salt = base64.b64decode(salt_b64)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32, # AES-256
salt=salt,
iterations=iterations,
)
return kdf.derive(password.encode('utf-8'))
# === 完整流程:前端用密码加密 → 后端用同一密码解密 ===
# 1. 前端:passwordBasedEncrypt(password, plaintext) → {salt, iv, data}
# 2. 后端:
# key = derive_aes_key(password, salt_b64)
# plaintext = decrypt_aes_gcm(key, iv, ciphertext)

五、AI 辅助安全审计:让 LLM 检查你的加密代码#

AI 编程工具(Claude、GPT-4)可以快速审查加密代码的常见错误。以下是一个实用的审计 prompt:

请审查以下前端加密代码的安全性,重点关注:
1. IV/nonce 是否随机且唯一
2. 密钥是否可导出(导出 = 潜在泄露)
3. 是否使用了已弃用的算法(如 DES、RC4、MD5)
4. 错误处理是否泄露了敏感信息
5. PBKDF2 迭代次数是否足够
6. 是否存在时序攻击风险
[粘贴你的代码]

AI 能找到的常见加密错误#

// ❌ AI 会立即指出:用 Math.random() 生成密钥
const weakKey = Math.random().toString(36).slice(2);
// Math.random() 不是密码学安全的随机数生成器!
// 攻击者可以预测输出
// ✅ 正确:用 crypto.getRandomValues
const strongKey = crypto.getRandomValues(new Uint8Array(32));
// ❌ AI 会指出:错误处理泄露了明文
try {
const decrypted = await decryptAES(key, iv, data);
} catch (e) {
console.error('解密失败,原文是:', plaintext); // 泄露!
// 或者:throw new Error(`Decryption failed for: ${data}`); // 密文泄露
}
// ✅ 正确:不泄露任何敏感信息
try {
const decrypted = await decryptAES(key, iv, data);
} catch (e) {
console.error('Decryption failed'); // 只记录事件,不记录数据
// 可选:上报安全事件(不含敏感内容)
reportSecurityEvent('DECRYPTION_FAILURE', { timestamp: Date.now() });
}

六、Docker 部署:加密服务的容器化#

将加密/解密服务容器化,确保环境一致性和安全隔离。

# === 多阶段构建:Python 加密后端 ===
FROM python:3.12-slim AS base
# 最小化攻击面:不安装不必要的包
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# 非 root 用户运行(安全最佳实践)
RUN useradd --create-home appuser
USER appuser
WORKDIR /home/appuser
# 只复制依赖文件,利用 Docker 缓存
COPY --chown=appuser:appuser requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=appuser:appuser . .
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml:加密服务 + Nginx 反向代理
version: '3.8'
services:
crypto-api:
build: .
restart: unless-stopped
# 环境变量不硬编码在镜像中
env_file: .env.production
# 限制资源(防止加密运算耗尽 CPU)
deploy:
resources:
limits:
cpus: '2.0'
memory: 512M
# 不暴露到宿主机,只通过 Nginx 访问
networks:
- internal
nginx:
image: nginx:1.25-alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- crypto-api
networks:
- internal
- external
networks:
internal:
driver: bridge
external:
driver: bridge

性能对比:Web Crypto vs JavaScript 库#

操作Web Crypto APIcrypto-js差距
AES-256 加密 1MB3.2ms45ms14x
SHA-256 哈希 1MB1.8ms32ms18x
PBKDF2 600K 迭代280ms4200ms15x
ECDSA 签名8ms120ms15x

测试环境:Chrome 124, M2 MacBook Pro, 2024-03。Web Crypto API 的 C++ 实现在所有操作上都有数量级优势。

完整实战项目:端到端加密笔记应用#

// === encrypted-notes.js ===
// 一个完整的端到端加密笔记应用核心逻辑
class EncryptedNotes {
constructor() {
this.dbName = 'EncryptedNotesDB';
this.storeName = 'notes';
}
/**
* 初始化:从密码派生主密钥
*/
async init(password) {
// 检查是否已有盐值(首次使用 vs 重新登录)
let salt = localStorage.getItem('note_salt');
if (!salt) {
// 首次使用:生成新盐值
const newSalt = crypto.getRandomValues(new Uint8Array(16));
salt = this._ab2b64(newSalt.buffer);
localStorage.setItem('note_salt', salt);
}
// 派生主密钥(不可导出)
const saltBytes = this._b642ab(salt);
this.masterKey = await this._deriveKey(password, saltBytes);
// 验证密码是否正确(尝试解密测试数据)
const testCipher = localStorage.getItem('note_test');
if (testCipher) {
try {
const { iv, data } = JSON.parse(testCipher);
await this.decrypt(
this.masterKey,
this._b642ab(iv),
this._b642ab(data)
);
} catch {
throw new Error('密码错误');
}
} else {
// 首次使用:写入测试数据
const testEncrypted = await this.encrypt(this.masterKey, 'init');
localStorage.setItem('note_test', JSON.stringify({
iv: this._ab2b64(testEncrypted.iv),
data: this._ab2b64(testEncrypted.ciphertext)
}));
}
}
/**
* 加密笔记
*/
async encrypt(key, plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode(plaintext)
);
return { iv: iv.buffer, ciphertext };
}
/**
* 解密笔记
*/
async decrypt(key, iv, ciphertext) {
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(iv) },
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
/**
* 保存加密笔记到 IndexedDB
*/
async saveNote(id, plaintext) {
const { iv, ciphertext } = await this.encrypt(this.masterKey, plaintext);
const db = await this._openDB();
const tx = db.transaction([this.storeName], 'readwrite');
const store = tx.objectStore(this.storeName);
store.put({
id,
iv: this._ab2b64(iv),
data: this._ab2b64(ciphertext),
updatedAt: Date.now()
});
return new Promise((resolve, reject) => {
tx.oncomplete = resolve;
tx.onerror = reject;
});
}
/**
* 读取并解密笔记
*/
async loadNote(id) {
const db = await this._openDB();
const tx = db.transaction([this.storeName], 'readonly');
const store = tx.objectStore(this.storeName);
return new Promise((resolve, reject) => {
const req = store.get(id);
req.onsuccess = async () => {
if (!req.result) { resolve(null); return; }
const plaintext = await this.decrypt(
this.masterKey,
this._b642ab(req.result.iv),
this._b642ab(req.result.data)
);
resolve(plaintext);
};
req.onerror = reject;
});
}
// === 辅助方法 ===
async _deriveKey(password, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2', false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false, ['encrypt', 'decrypt']
);
}
_openDB() {
return new Promise((resolve, reject) => {
const req = indexedDB.open(this.dbName, 1);
req.onupgradeneeded = (e) => {
e.target.result.createObjectStore(this.storeName, { keyPath: 'id' });
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
_ab2b64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
_b642ab(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;
}
}
// === 使用 ===
const notes = new EncryptedNotes();
await notes.init('我的强密码!@#2026');
await notes.saveNote('note-1', '这是一条加密笔记,服务端看不到内容');
const content = await notes.loadNote('note-1');
console.log(content); // '这是一条加密笔记,服务端看不到内容'

总结与最佳实践清单#

最佳实践说明
✅ 始终使用 crypto.getRandomValues不用 Math.random() 生成密钥/IV
✅ AES-GCM 的 IV 每次必须唯一重用 IV = 密钥泄露
✅ PBKDF2 迭代 ≥ 600,000OWASP 2023 最低推荐
✅ 密钥设置 extractable: false防止密钥被导出窃取
✅ 错误信息不包含敏感数据防止信息泄露
✅ 大数据用混合加密RSA 只能加密 ~190 字节
✅ 服务端验证签名前端签名需后端交叉验证
❌ 不要在前端存储长期密钥用 PBKDF2 从密码派生
❌ 不要使用已弃用算法DES、RC4、MD5、SHA-1
❌ 不要自己实现加密算法用标准库,不要造轮子

Web Crypto API 是浏览器原生提供的最强密码学工具。它的性能远超 JavaScript 实现,安全性经过严格审查。掌握它,你就能在前端构建真正的端到端加密应用。

最后提醒:加密只是安全的一环。完整的系统安全还需要 HTTPS、CSP、XSS 防护、CSRF 防护等多层防御。加密不能替代其他安全措施,它是最后一道防线。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Web Crypto API 前端加密实战:从理论到生产环境
https://boke.hackerdream.xyz/posts/web-crypto-api-frontend-encryption-practical-guide/
作者
晴天
发布于
2026-05-19
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
125
分类
17
标签
287
总字数
257,955
运行时长
0
最后活动
0 天前

目录