Skip to content

指南 · 进阶

版本基线 crypto-js 4.2.0。把 crypto-js 用进真实项目:按需引入与体积、各算法实战、与 OpenSSL 互通、解裸密文、渐进式哈希、TypeScript、常见坑排查。

速查

  • 子路径引入:crypto-js/sha256crypto-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 每个算法是独立子模块,按子路径只引用到的,可显著瘦身:

ts
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 模式:

ts
// 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:

bash
# 命令行加密
echo -n "Message" | openssl enc -aes-256-cbc -e -base64 -pass pass:"Secret Passphrase" -md md5
ts
// 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:

ts
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

ts
const sha = CryptoJS.algo.SHA256.create();
for (const chunk of chunks) sha.update(chunk);
const hash = sha.finalize().toString();

复用同一 hasher 算多条独立消息时,每条 finalize() 后必须 sha.reset() 清空累积状态,否则下一条会接到旧状态后面:

ts
const h = CryptoJS.algo.SHA256.create();
const a = h.finalize("msgA").toString(); h.reset();
const b = h.finalize("msgB").toString();

六、PBKDF2 安全派生(推荐替代口令模式)

不要依赖口令模式默认的弱 EvpKDF。基于用户口令加密的正确做法:

ts
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

bash
npm install -D @types/crypto-js
ts
import 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 取舍、选型决策。