Skip to content

指南 · 专家

版本基线 crypto-js 4.2.0。深入安全内核:KDF 默认值与加固、密码存储为何不能用通用哈希、时序攻击与常量时间比较、加密模式的安全取舍、与 Web Crypto 的对比、选型决策、维护现状再辨析。

速查

  • KDF 必须分清:4.2.0 PBKDF2 默认 SHA-256 / 250000 次,AES 字符串口令模式仍走 MD5 / 1 次的 EvpKDF
  • 密码存储不要用 SHA-256 / SHA-512;应在服务端使用 Argon2、scrypt、bcrypt 或按策略配置的 PBKDF2
  • MAC / 签名比较不要自写“常量时间”JavaScript;Node 用 crypto.timingSafeEqual,Web Crypto 用 subtle.verify
  • CBC / CTR 等只提供机密性,没有认证标签;优先 AES-GCM,遗留协议才考虑 Encrypt-then-MAC
  • CryptoJS.SHA3 是 Keccak[c=2d],与 NIST SHA3-224 / 256 / 384 / 512 的 padding 和结果不同
  • 4.x 随机盐与 IV 来自原生 Crypto,不会回退 Math.random();原生随机源缺失时抛错
  • 前端持有对称密钥不等于端到端安全,前端加密不能替代 TLS、密钥管理与服务端授权
  • 项目已停止维护且缺 AEAD;仅为存量格式互通或受控兼容保留,新系统迁移到平台加密 API

一、两个 KDF 的默认值(必须分清)

crypto-js 有两个密钥派生函数,默认值天差地别

KDF默认 hasher默认 iterations谁在用
PBKDF2SHA256250000你显式调用时(推荐)
EvpKDFMD51口令模式 AES.encrypt(msg, 'passphrase') 内部

关键结论:

  • 口令模式底层是 EvpKDF(MD5 + 单次迭代),对暴力破解几乎没有抵抗力——它存在的意义是 OpenSSL 兼容,不是真正的口令防护。
  • PBKDF24.2.0 修正了危险默认值:更早版本默认 SHA-1 + 极低迭代,4.2.0 改为 SHA-256 + 250000 次
  • 要安全地保护口令,显式用 PBKDF2 大迭代派生 key,再以 WordArray key+IV 加密(见进阶篇)。
ts
// 弱:口令模式默认走 EvpKDF(MD5/1 次)
CryptoJS.AES.encrypt("msg", "passphrase");

// 强:PBKDF2 显式派生
const key = CryptoJS.PBKDF2("passphrase", salt, { keySize: 256 / 32, iterations: 250000 });
CryptoJS.AES.encrypt("msg", key, { iv });

二、为什么哈希不能直接存密码

SHA256(password) 当密码存储是经典错误:

  • 通用哈希(MD5/SHA*)为速度而设计,攻击者每秒可尝试上亿次,配合彩虹表/字典能快速反推弱口令。
  • 不加盐时,相同口令哈希相同,一张彩虹表打穿一片用户。
  • 输出再长(SHA-512)也**不解决「快」**的问题。

正确做法:用专为慢而设计的密码哈希——bcrypt / scrypt / Argon2,或至少 PBKDF2 + 大迭代 + 每用户随机 salt。crypto-js 提供 PBKDF2,但密码这类高安全场景更应放在服务端并用经审计的实现。

一句话:哈希的「不可逆」≠「抗猜测」。快哈希可被穷举,慢哈希才能拖垮暴力破解。

三、时序攻击与常量时间比较

校验 HMAC/签名时,用普通 === 或字符串比较可能短路(发现第一个不同字符就返回),比对耗时随匹配前缀长度变化,理论上可被时序攻击逐位试探出正确值。

应使用平台提供并经过审查的常量时间比较 / 验签 API。crypto-js 不内置该工具,也不要把普通 JavaScript 循环包装成“绝对常量时间”实现,因为 JIT 优化和运行时行为不受应用代码控制:

ts
import { timingSafeEqual } from "node:crypto";

const valid = actual.length === expected.length
  && timingSafeEqual(actual, expected);

浏览器端若使用 Web Crypto 计算 HMAC,可直接用 crypto.subtle.verify() 完成校验,避免先转字符串再自行比较。

四、加密模式的安全取舍

模式需 IV需填充安全提示
CBC(默认)是(Pkcs7)IV 必须随机唯一;注意 padding oracle 风险
CTR / OFB / CFB是(nonce)否(流式)明文任意长、密文等长;nonce 绝不可复用
ECB⚠️ 相同明文块→相同密文块,泄露结构,勿用

共同短板:crypto-js 的这些模式都不提供完整性认证(不是 AEAD)。这意味着密文可被篡改而无法察觉。需要认证时:

  • 自己加 HMAC(Encrypt-then-MAC:先加密、再对密文算 HMAC、一起存,校验通过才解密);
  • 或直接改用原生 AES-GCM(自带认证标签),这正是新项目优先 Web Crypto 的重要理由。

五、crypto-js vs 原生 Web Crypto / Node crypto

维度crypto-jsWeb Crypto / Node crypto
实现纯 JS原生(C/OpenSSL),常硬件加速
API同步、极简异步(Promise)/ Node 同步可选
性能较慢
AEAD(GCM)❌ 不支持✅ 支持 AES-GCM
安全随机4.x 包装原生 getRandomValues / randomBytes,缺失即报错直接提供 getRandomValues / randomBytes
兼容性算法为纯 JS,但随机相关 API 要求环境提供原生 Crypto取决于具体平台与算法支持
维护❌ 已停更✅ 平台持续维护

结论:新项目的安全场景优先原生(Node 用内置 crypto,浏览器用 Web Crypto);crypto-js 留给「无原生能力的受限环境、需同步 API、与既有 crypto-js/OpenSSL 数据互通」的场景。

六、前端加密的安全边界

「前端用 crypto-js 加密一下,服务器就拿不到明文」是常见误区,错在:

  1. 密钥若在前端(写死或前端派生后发往服务器),任何拿到前端代码/流量的人都能得到密钥,加密形同虚设;
  2. 传输安全应交给 HTTPS/TLS,前端加密替代不了它;
  3. crypto-js 已停更且缺 AEAD;即便 4.x 已改用原生安全随机源,也没有解决协议设计与维护风险。

前端对称加密只在端到端、密钥始终不离开用户/不经服务器的特定模型下才有意义,且这类高安全需求更应用经过审计的方案与原生加密。

七、选型决策清单

适合继续用 crypto-js:

  • 对接历史上用 crypto-js 或 openssl enc 口令模式加密的存量数据
  • 在受控环境处理既有同步调用,且已经明确算法、随机源和完整性保护边界
  • 只做非安全用途的兼容校验和 / 内容指纹,并接受停更依赖风险

应改用原生 / 专用方案:

  • 存储用户密码 → 服务端 bcrypt/argon2/大迭代 PBKDF2
  • 需要 AES-GCM 等认证加密 → 原生 Web Crypto / Node crypto
  • 追求极致性能的大批量加密 → 原生
  • 任何对安全有硬性要求的新系统 → 优先原生 + 经审计方案

八、维护现状再辨析

官方 README 原文:「Active development of CryptoJS has been discontinued. This library is no longer maintained.」——停止主动开发、不再维护,但要准确理解:

  • 不是「删库下架」:包仍在 npm 上可正常安装(最新 4.2.0),存量项目能继续跑。
  • 理由:如今 Node 与现代浏览器都内置原生 Crypto,继续开发 crypto-js 只会让它沦为原生 Crypto 的包装层;且原生模块自带安全随机数(Math.random 不安全)。
  • 影响:不再有安全更新、不会新增 AEAD 等现代特性——这正是新项目安全场景应转向原生的根本原因。

回到 入门 复习用法,或查 参考 速览 API、模式、填充与编码器。