crypto-js、jose、jsonwebtoken、jws 和 node-jose 都是 JavaScript 生态中用于处理加密、哈希和 JSON Web Token(JWT)的流行库,但它们的设计目标、适用场景和维护状态存在显著差异。crypto-js 是一个通用的前端加密工具箱,支持多种加密算法,但不处理 JWT。其余四个库专注于 JWT 相关功能,其中 jose 是现代、标准兼容且跨平台的首选,jsonwebtoken 主要面向 Node.js 且不支持加密令牌(JWE),jws 是底层签名库通常不直接用于应用开发,而 node-jose 已被官方弃用,不应在新项目中使用。
在现代 Web 应用中,处理加密、哈希、数字签名和 JSON Web Token(JWT)是常见需求。然而,前端开发者面对多个 npm 包时,常因功能重叠、API 差异或维护状态而难以抉择。本文将深入比较 crypto-js、jose、jsonwebtoken、jws 和 node-jose,从实际工程角度出发,帮助你做出合理的技术选型。
首先必须明确:node-jose 已被官方标记为弃用。其 GitHub 仓库和 npm 页面均指出,该库不再维护,推荐迁移到 jose。因此,新项目绝不应使用 node-jose。
同样,jws 是一个底层签名库,通常不直接用于应用层开发,而是作为 jsonwebtoken 的依赖存在。除非你正在实现自定义 JWT 签名逻辑,否则一般无需直接引入它。
这些库可分为两类:
crypto-jsjose、jsonwebtoken、jws、node-jose(已弃用)crypto-js:前端友好的加密工具箱crypto-js 是一个纯 JavaScript 实现的加密库,支持 AES、DES、SHA、HMAC、PBKDF2 等多种算法。它专为浏览器环境设计,无需 Node.js 内置模块,适合在前端进行本地加密、哈希或密钥派生。
// crypto-js:AES 加密
import CryptoJS from 'crypto-js';
const message = 'Hello, world!';
const key = 'secret-key-123';
const encrypted = CryptoJS.AES.encrypt(message, key).toString();
const decrypted = CryptoJS.AES.decrypt(encrypted, key).toString(CryptoJS.enc.Utf8);
注意:crypto-js 不支持 JWT。它无法解析、验证或签发 JWT,仅提供底层加密原语。
JWT 库的核心能力包括:
我们逐一分析。
jsonwebtoken:Node.js 优先,前端需谨慎jsonwebtoken 是最流行的 JWT 库之一,但主要面向 Node.js 环境。它依赖 Node.js 的 crypto 模块,在浏览器中需通过 polyfill(如 @peculiar/webcrypto + bundler 配置)才能运行,且不支持 JWE(加密令牌)。
// jsonwebtoken:签发 JWT(Node.js 示例)
import jwt from 'jsonwebtoken';
const token = jwt.sign({ id: 123 }, 'secret', { expiresIn: '1h' });
const decoded = jwt.verify(token, 'secret');
在前端使用时,若未正确配置 polyfill,会报错 Cannot find module 'crypto'。此外,它仅支持对称密钥(HMAC)和非对称密钥(RSA/ECDSA)的 JWS,不支持 JWE。
jose:现代、标准兼容、全平台支持jose 是当前最推荐的 JWT 库。它完全遵循 IETF 的 JOSE(JSON Object Signing and Encryption)标准,支持 JWS 和 JWE,并且原生支持浏览器和 Node.js,无需额外 polyfill。其 API 设计清晰,支持 Web Crypto API。
// jose:签发 JWS(浏览器/Node.js 通用)
import { SignJWT } from 'jose';
const secret = new TextEncoder().encode('secret');
const token = await new SignJWT({ id: 123 })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('1h')
.sign(secret);
// 验证
import { jwtVerify } from 'jose';
const { payload } = await jwtVerify(token, secret);
jose 还支持公钥/私钥对(如 RS256、ES256)和加密令牌(JWE),适合需要高安全性的场景。
jws:底层签名,不推荐直接使用jws 仅提供 JWS(JSON Web Signature)的底层实现,用于创建和验证签名,但不处理 JWT 的声明(claims)或过期逻辑。通常作为 jsonwebtoken 的内部依赖存在。
// jws:手动构建 JWS
import jws from 'jws';
const header = { alg: 'HS256' };
const payload = { id: 123, iat: Math.floor(Date.now() / 1000) };
const signingInput = [header, payload].map(JSON.stringify).join('.');
const signature = /* 手动 HMAC-SHA256 */;
const token = `${signingInput}.${signature}`;
// 验证需手动解析并比对签名
这种方式繁琐且易出错,不建议在应用层直接使用 jws。
node-jose:已弃用,切勿使用如前所述,node-jose 官方已弃用。其 API 虽然功能完整(支持 JWS/JWE),但存在安全漏洞风险且无后续更新。所有新项目应选择 jose 作为替代。
crypto-js:纯 JS,无依赖,打包后体积可控,适合前端。jose:使用现代 ES 模块,支持 tree-shaking,浏览器原生可用(依赖 Web Crypto API,现代浏览器均支持)。jsonwebtoken:依赖 Node.js crypto,在前端需额外配置,增加构建复杂度。jws / node-jose:前者为底层库,后者已弃用,均不推荐用于新项目。jose 进行验证:若需在前端验证 JWT(例如检查过期时间),jose 提供了安全、标准的 API。// 前端安全验证示例(使用公钥)
import { jwtVerify } from 'jose';
const publicKey = await crypto.subtle.importKey(
'jwk',
{ kty: 'RSA', ... }, // 来自服务端的 JWK
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
false,
['verify']
);
const { payload } = await jwtVerify(token, publicKey);
| 场景 | 推荐库 | 理由 |
|---|---|---|
| 前端本地加密/哈希(如密码哈希、AES 加密) | crypto-js | 纯 JS,无依赖,浏览器友好 |
| 签发或验证 JWT(全平台) | jose | 标准兼容,支持 JWS/JWE,原生浏览器支持 |
| Node.js 后端签发 JWT(简单场景) | jsonwebtoken | 社区广泛,API 简洁(但注意不支持 JWE) |
| 需要 JWE(加密令牌) | jose | 唯一活跃且标准兼容的选择 |
| 新项目 | 避免 node-jose 和 jws | 前者已弃用,后者过于底层 |
jose;若只需加密/哈希,用 crypto-js。jose 可减少上下文切换,提升一致性。node-jose 或 jsonwebtoken(需 JWE 时)逐步替换为 jose。记住:安全始于正确的工具选择。在加密和身份验证领域,使用活跃维护、标准兼容的库是保障应用安全的第一步。
选择 crypto-js 如果你需要在前端执行通用的加密、解密、哈希或密钥派生操作(如 AES、SHA、HMAC),且不需要处理 JWT。它是一个纯 JavaScript 库,无需 Node.js 环境,适合浏览器场景,但要注意它不提供任何 JWT 功能。
选择 jose 如果你需要在浏览器或 Node.js 中处理 JWT(包括签发、验证、解码),尤其是需要支持 JWS(签名)和 JWE(加密)标准的现代应用。它是目前最活跃、最标准兼容的库,原生支持 Web Crypto API,适合新项目和需要高安全性的场景。
选择 jsonwebtoken 仅当你在 Node.js 后端开发中需要快速实现简单的 JWT 签发和验证,且不需要 JWE(加密令牌)功能。它在前端使用需额外配置 polyfill,不适合浏览器环境,且缺乏对现代 JOSE 标准的完整支持。
选择 jws 仅当你需要直接操作 JWS(JSON Web Signature)的底层签名逻辑,例如构建自定义 JWT 实现或调试签名过程。它不处理 JWT 声明(如过期时间)或验证逻辑,通常作为其他 JWT 库的依赖存在,不推荐在应用层直接使用。
不要选择 node-jose。该库已被官方标记为弃用,不再维护,存在潜在安全风险。所有新项目应使用 jose 作为替代,现有项目应制定迁移计划。
JavaScript library of crypto standards.
Active development of CryptoJS has been discontinued. This library is no longer maintained.
Nowadays, NodeJS and modern browsers have a native Crypto module. The latest version of CryptoJS already uses the native Crypto module for random number generation, since Math.random() is not crypto-safe. Further development of CryptoJS would result in it only being a wrapper of native Crypto. Therefore, development and maintenance has been discontinued, it is time to go for the native crypto module.
Requirements:
npm install crypto-js
ES6 import for typical API call signing use case:
import sha256 from 'crypto-js/sha256';
import hmacSHA512 from 'crypto-js/hmac-sha512';
import Base64 from 'crypto-js/enc-base64';
const message, nonce, path, privateKey; // ...
const hashDigest = sha256(nonce + message);
const hmacDigest = Base64.stringify(hmacSHA512(path + hashDigest, privateKey));
Modular include:
var AES = require("crypto-js/aes");
var SHA256 = require("crypto-js/sha256");
...
console.log(SHA256("Message"));
Including all libraries, for access to extra methods:
var CryptoJS = require("crypto-js");
console.log(CryptoJS.HmacSHA1("Message", "Key"));
Requirements:
bower install crypto-js
Modular include:
require.config({
packages: [
{
name: 'crypto-js',
location: 'path-to/bower_components/crypto-js',
main: 'index'
}
]
});
require(["crypto-js/aes", "crypto-js/sha256"], function (AES, SHA256) {
console.log(SHA256("Message"));
});
Including all libraries, for access to extra methods:
// Above-mentioned will work or use this simple form
require.config({
paths: {
'crypto-js': 'path-to/bower_components/crypto-js/crypto-js'
}
});
require(["crypto-js"], function (CryptoJS) {
console.log(CryptoJS.HmacSHA1("Message", "Key"));
});
<script type="text/javascript" src="path-to/bower_components/crypto-js/crypto-js.js"></script>
<script type="text/javascript">
var encrypted = CryptoJS.AES(...);
var encrypted = CryptoJS.SHA256(...);
</script>
See: https://cryptojs.gitbook.io/docs/
var CryptoJS = require("crypto-js");
// Encrypt
var ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123').toString();
// Decrypt
var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log(originalText); // 'my message'
var CryptoJS = require("crypto-js");
var data = [{id: 1}, {id: 2}]
// Encrypt
var ciphertext = CryptoJS.AES.encrypt(JSON.stringify(data), 'secret key 123').toString();
// Decrypt
var bytes = CryptoJS.AES.decrypt(ciphertext, 'secret key 123');
var decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log(decryptedData); // [{id: 1}, {id: 2}]
crypto-js/corecrypto-js/x64-corecrypto-js/lib-typedarrayscrypto-js/md5crypto-js/sha1crypto-js/sha256crypto-js/sha224crypto-js/sha512crypto-js/sha384crypto-js/sha3crypto-js/ripemd160crypto-js/hmac-md5crypto-js/hmac-sha1crypto-js/hmac-sha256crypto-js/hmac-sha224crypto-js/hmac-sha512crypto-js/hmac-sha384crypto-js/hmac-sha3crypto-js/hmac-ripemd160crypto-js/pbkdf2crypto-js/aescrypto-js/tripledescrypto-js/rc4crypto-js/rabbitcrypto-js/rabbit-legacycrypto-js/evpkdfcrypto-js/format-opensslcrypto-js/format-hexcrypto-js/enc-latin1crypto-js/enc-utf8crypto-js/enc-hexcrypto-js/enc-utf16crypto-js/enc-base64crypto-js/mode-cfbcrypto-js/mode-ctrcrypto-js/mode-ctr-gladmancrypto-js/mode-ofbcrypto-js/mode-ecbcrypto-js/pad-pkcs7crypto-js/pad-ansix923crypto-js/pad-iso10126crypto-js/pad-iso97971crypto-js/pad-zeropaddingcrypto-js/pad-nopaddingChange default hash algorithm and iteration's for PBKDF2 to prevent weak security by using the default configuration.
Custom KDF Hasher
Blowfish support
Fix module order in bundled release.
Include the browser field in the released package.json.
Added url safe variant of base64 encoding. 357
Avoid webpack to add crypto-browser package. 364
This is an update including breaking changes for some environments.
In this version Math.random() has been replaced by the random methods of the native crypto module.
For this reason CryptoJS might not run in some JavaScript environments without native crypto module. Such as IE 10 or before or React Native.
Rollback, 3.3.0 is the same as 3.1.9-1.
The move of using native secure crypto module will be shifted to a new 4.x.x version. As it is a breaking change the impact is too big for a minor release.
The usage of the native crypto module has been fixed. The import and access of the native crypto module has been improved.
In this version Math.random() has been replaced by the random methods of the native crypto module.
For this reason CryptoJS might does not run in some JavaScript environments without native crypto module. Such as IE 10 or before.
If it's absolute required to run CryptoJS in such an environment, stay with 3.1.x version. Encrypting and decrypting stays compatible. But keep in mind 3.1.x versions still use Math.random() which is cryptographically not secure, as it's not random enough.
This version came along with CRITICAL BUG.
DO NOT USE THIS VERSION! Please, go for a newer version!
The 3.1.x are based on the original CryptoJS, wrapped in CommonJS modules.