Web Crypto API 前端加密实战:从理论到生产环境
为什么前端需要加密?
很多开发者认为「前端不安全,加密没意义」——这个观点只对了一半。确实,前端代码对用户完全透明,你无法在前端隐藏密钥。但前端加密的核心价值不在于「防用户」,而在于:
- 传输层加密:即使 HTTPS 被中间人攻击(公共 WiFi、企业代理),客户端加密的数据仍然安全
- 零知识架构:服务端只存储密文,永远不知道用户的明文数据(如密码管理器、加密笔记)
- 数据完整性:通过签名验证数据未被篡改
- 合规要求: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.subtle为undefined。
一、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 的 IVawait crypto.subtle.encrypt({ name: 'AES-GCM', iv: FIXED_IV }, key, data);// 攻击者可以通过对比密文推断明文是否相同!
// ❌ 错误:IV 太短const iv = crypto.getRandomValues(new Uint8Array(8)); // 应该用 12 字节// GCM 模式推荐 12 字节 IV,其他长度会触发额外的 GHASH 计算,降低性能
// ✅ 正确:每次加密都生成新 IVconst 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 AESGCMimport base64import 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, serializationfrom cryptography.hazmat.primitives.asymmetric import ecimport 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 PBKDF2HMACfrom cryptography.hazmat.primitives import hashesimport 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.getRandomValuesconst 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 appuserUSER appuserWORKDIR /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 8000CMD ["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 API | crypto-js | 差距 |
|---|---|---|---|
| AES-256 加密 1MB | 3.2ms | 45ms | 14x |
| SHA-256 哈希 1MB | 1.8ms | 32ms | 18x |
| PBKDF2 600K 迭代 | 280ms | 4200ms | 15x |
| ECDSA 签名 | 8ms | 120ms | 15x |
测试环境: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,000 | OWASP 2023 最低推荐 |
✅ 密钥设置 extractable: false | 防止密钥被导出窃取 |
| ✅ 错误信息不包含敏感数据 | 防止信息泄露 |
| ✅ 大数据用混合加密 | RSA 只能加密 ~190 字节 |
| ✅ 服务端验证签名 | 前端签名需后端交叉验证 |
| ❌ 不要在前端存储长期密钥 | 用 PBKDF2 从密码派生 |
| ❌ 不要使用已弃用算法 | DES、RC4、MD5、SHA-1 |
| ❌ 不要自己实现加密算法 | 用标准库,不要造轮子 |
Web Crypto API 是浏览器原生提供的最强密码学工具。它的性能远超 JavaScript 实现,安全性经过严格审查。掌握它,你就能在前端构建真正的端到端加密应用。
最后提醒:加密只是安全的一环。完整的系统安全还需要 HTTPS、CSP、XSS 防护、CSRF 防护等多层防御。加密不能替代其他安全措施,它是最后一道防线。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!