指南 · 进阶
版本基线 crypto-js 4.2.0。把 crypto-js 用进真实项目:按需引入与体积、各算法实战、与 OpenSSL 互通、解裸密文、渐进式哈希、TypeScript、常见坑排查。
速查
- 子路径引入:
crypto-js/sha256、crypto-js/aes等可缩小依赖面;主包是 UMD / CommonJS,类型来自@types/crypto-js - 外部 key / IV 必须先按约定用 Hex 或 Base64 解析;AES key 只能是 16 / 24 / 32 字节,CBC IV 为 16 字节
- OpenSSL 互通要显式对齐算法、模式、KDF、摘要、salt 与编码;不要依赖不同 OpenSSL 版本的命令默认值
- 裸 Base64 密文要先放进
CipherParams.create({ ciphertext }),再用同一 key、IV、mode、padding 解密 - 大数据摘要可用
algo.SHA256.create()分块update();独立消息之间重用实例要显式reset() - 口令加密应显式 PBKDF2 派生 key,并把 salt、IV、迭代数和算法版本与密文一起保存
WordArray.random()在 4.x 依赖原生安全随机源;没有getRandomValues/randomBytes的环境会失败而非降级- 安全新系统优先 Web Crypto / Node
crypto的 AES-GCM、HMAC 与平台验签;crypto-js 主要用于存量互通
一、按需引入:减小打包体积
整包 import CryptoJS from "crypto-js" 会把所有算法(全部哈希/密码/模式/编码器)打进产物。crypto-js 每个算法是独立子模块,按子路径只引用到的,可显著瘦身:
import sha256 from "crypto-js/sha256";
import hmacSHA256 from "crypto-js/hmac-sha256";
import AES from "crypto-js/aes";
import encBase64 from "crypto-js/enc-base64";
import encUtf8 from "crypto-js/enc-utf8";
import modeCTR from "crypto-js/mode-ctr";
import padNoPadding from "crypto-js/pad-nopadding";
import PBKDF2 from "crypto-js/pbkdf2";子模块名对应 README 的子路径(crypto-js/<算法>)。
若需求只是少数算法且追求体积/现代化,也可考虑零打包体积的原生 Web Crypto——它内置于浏览器,不进 bundle。
二、AES 实战:用外部给的 key/IV
外部(如密钥管理服务、其它语言后端)通常以 hex 或 Base64 给 key/IV,要先 parse 成 WordArray 再走 key 模式:
// 32 字节(256 位)hex key → AES-256;16 字节 hex IV
const key = CryptoJS.enc.Hex.parse("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
const iv = CryptoJS.enc.Hex.parse("101112131415161718191a1b1c1d1e1f");
const ct = CryptoJS.AES.encrypt("Message", key, { iv }).ciphertext.toString(CryptoJS.enc.Base64);要点:
- key 的字节长度决定 AES 位数:16→AES-128、24→AES-192、32→AES-256。
- 此为直接密钥模式,不产生 salt;解密端需用同一 key+IV。
- 若 key/IV 是 Base64,就用
CryptoJS.enc.Base64.parse(...)。
三、与 OpenSSL 命令行互通
口令模式默认即 OpenSSL 兼容格式(Salted__ + salt + 密文,Base64),用 EVP_BytesToKey(EvpKDF/MD5)派生 key+IV:
# 命令行加密
echo -n "Message" | openssl enc -aes-256-cbc -e -base64 -pass pass:"Secret Passphrase" -md md5// crypto-js 解密上面的输出(口令模式可直接传 OpenSSL 字符串,自动解析 salt)
const pt = CryptoJS.AES.decrypt(opensslBase64, "Secret Passphrase").toString(CryptoJS.enc.Utf8);互通条件:① 两端口令一致;② 算法/密钥长度/模式匹配(如都 AES-256-CBC);③ KDF、摘要、salt 与 Base64 参数一致。OpenSSL 不同版本的默认摘要与弃用提示会变化;与 crypto-js 口令模式互通时应显式使用传统 KDF 对应的 -md md5,不要依赖命令默认值。若命令启用 -pbkdf2,就不能直接交给 crypto-js 的默认口令模式解密。
四、解密「裸密文」:构造 CipherParams
AES.decrypt 在口令模式下能直接吃 OpenSSL 字符串(自动取 salt)。但如果你手上是不含 salt 头的裸 Base64 密文(key 模式产生、或外部系统给的),就要手动构造 CipherParams:
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(rawBase64),
});
const pt = CryptoJS.AES
.decrypt(cipherParams, key, { iv }) // 提供对应 key + IV
.toString(CryptoJS.enc.Utf8);⚠️ 别把裸密文直接当口令模式字符串丢进去——那会被当成 OpenSSL 格式去找 salt,必然失败。
五、渐进式哈希:处理大数据/分块
一次性 CryptoJS.SHA256(x) 适合小数据。分块(如流式读取)用 algo:
const sha = CryptoJS.algo.SHA256.create();
for (const chunk of chunks) sha.update(chunk);
const hash = sha.finalize().toString();复用同一 hasher 算多条独立消息时,每条 finalize() 后必须 sha.reset() 清空累积状态,否则下一条会接到旧状态后面:
const h = CryptoJS.algo.SHA256.create();
const a = h.finalize("msgA").toString(); h.reset();
const b = h.finalize("msgB").toString();六、PBKDF2 安全派生(推荐替代口令模式)
不要依赖口令模式默认的弱 EvpKDF。基于用户口令加密的正确做法:
function encryptWithPassphrase(plaintext: string, passphrase: string) {
const salt = CryptoJS.lib.WordArray.random(128 / 8);
const iv = CryptoJS.lib.WordArray.random(128 / 8);
const key = CryptoJS.PBKDF2(passphrase, salt, { keySize: 256 / 32, iterations: 250000 });
const ct = CryptoJS.AES.encrypt(plaintext, key, { iv });
// salt 与 IV 不需保密,与密文一起存
return {
salt: salt.toString(CryptoJS.enc.Hex),
iv: iv.toString(CryptoJS.enc.Hex),
ciphertext: ct.ciphertext.toString(CryptoJS.enc.Base64),
};
}解密时用同样的 salt 重派生 key、同样的 IV 解密。
七、TypeScript 用法
crypto-js 主包是 UMD/CJS,类型不在主包内,需另装社区维护的 @types/crypto-js:
npm install -D @types/crypto-jsimport CryptoJS from "crypto-js";
const hash: string = CryptoJS.SHA256("msg").toString();
const wa: CryptoJS.lib.WordArray = CryptoJS.enc.Utf8.parse("Hello");因库已停更,类型也可能滞后于边缘 API;遇到类型缺失可用
as或补充声明。
八、常见坑排查清单
| 现象 | 原因 | 处理 |
|---|---|---|
| 解密结果是一串 Hex/乱码 | 漏了 toString(CryptoJS.enc.Utf8) | 解密后显式按 Utf8 解码 |
| 「Malformed UTF-8 data」 | 解密失败(key/IV/mode/padding 不匹配或密文损坏) | 核对加解密参数一一对应 |
| 口令加密的密文 key 模式解不开 | 两种模式混用 | 统一用口令或统一用 key+IV |
| 末尾多出奇怪字节 | 解密端 padding 与加密端不一致(如设了 NoPadding) | 两端 padding 必须一致 |
| 跨端哈希对不上 | 两边「文本→字节」编码不同 | 统一用 UTF-8 |
| NoPadding 下密文异常 | 明文不是块大小整数倍 | 改用 Pkcs7 或自行对齐到 16 字节 |
进入 指南 · 专家:KDF 默认值与安全加固、密码存储为何不能用哈希、时序攻击与常量时间比较、Web Crypto 取舍、选型决策。