0. 先看懂官方 node:crypto 模块
这一章先回答一个范围问题:本教程中的 crypto,具体指什么?
0.1 它是 Node.js 内置模块
官方模块的推荐导入方式带有 node: 前缀:
import * as crypto from "node:crypto";
const token = crypto.randomBytes(32).toString("base64url");
也可以只导入实际使用的接口:
import {
createHash,
randomBytes,
timingSafeEqual,
} from "node:crypto";
两种写法都来自同一个官方内置模块,不需要安装依赖。node: 前缀能清楚告诉读者:这是 Node.js 核心模块,而不是项目目录或 npm 包。
不要执行:
npm install crypto
Node.js 已经提供 node:crypto。安装名称相似的 npm 包既没有必要,也容易混淆代码实际使用的是哪一份实现。
0.2 node:crypto 与 Web Crypto 不是同一套接口
Node.js 里可能看到两种风格:
// 本教程重点:Node.js 传统 crypto API
import { createHash } from "node:crypto";
// Web 标准风格的 Web Crypto API
const webCrypto = globalThis.crypto;
await webCrypto.subtle.digest("SHA-256", data);
它们都提供密码学能力,但接口模型不同:
| 对比 | node:crypto |
Web Crypto API |
|---|---|---|
| 设计来源 | Node.js 核心 API | Web 标准 |
| 常见入口 | import ... from "node:crypto" |
globalThis.crypto.subtle |
| 数据类型 | 常用 Buffer,也接受多种字节视图 |
常用 ArrayBuffer、TypedArray |
| 调用风格 | 对象、流、回调、同步和一次性函数并存 | 主要是 Promise |
| 适用场景 | Node.js 服务端和既有生态 | 希望与浏览器共享标准接口 |
本教程围绕用户指定的官方 node:crypto 页面展开。不要把两个 API 的方法名和参数直接混用。
0.3 官方 API 看起来很多,怎样分组
官方文档既包含日常业务常用方法,也包含密钥交换、证书、素数和底层兼容接口。初学时不需要按页面从头背到尾,可以先按问题分组:
第一组:随机数据
randomBytes() 随机字节,常用于 Token、Key、Salt、IV
randomInt() 指定范围内的安全随机整数
randomUUID() UUID v4 标识
randomFill() 用随机数据填充已有 Buffer 或 TypedArray
这是 Web 服务中最高频的一组。
第二组:哈希与消息认证
createHash() 创建 Hash 对象
createHmac() 创建带共享 Secret 的 HMAC 对象
timingSafeEqual() 比较敏感字节
它们用于数据指纹、随机 Token 摘要、Webhook 验证和 JWT HS256 的底层原理。
第三组:密码派生
scrypt() / scryptSync()
pbkdf2() / pbkdf2Sync()
hkdf() / hkdfSync()
这些名字都与“派生出一段密钥材料”有关,但用途不能互换:
scrypt()故意消耗内存和计算资源,适合密码哈希或从密码派生密钥。pbkdf2()是常见的基于密码的派生函数,兼容既有协议时经常遇到。hkdf()用于从已有高质量密钥材料派生不同用途的子密钥,不用于直接保存弱密码。
最新版官方文档可能还有内置 argon2()。使用前必须核对实际 Node.js 版本和稳定级别;本项目的 Node 20 类型范围不包含它,因此密码示例使用稳定且兼容范围更广的 scrypt()。
第四组:对称加密
createCipheriv() 创建加密器
createDecipheriv() 创建解密器
Cipheriv / Decipheriv 对象的 update()、final() 等方法
现代应用不要使用已弃用或隐式从密码生成 Key/IV 的旧式 createCipher()。教程使用显式传入 Key 和 IV 的 createCipheriv(),并选择 AES-GCM 认证加密。
第五组:密钥与非对称密码学
createSecretKey() / createPrivateKey() / createPublicKey()
generateKey() / generateKeyPair()
KeyObject
sign() / verify()
publicEncrypt() / privateDecrypt()
KeyObject 是 Node.js 对密钥的结构化表示,能区分 secret、private、public 等类型。比起在业务代码中四处传裸字符串,它能更明确地表达“这是一把什么密钥”。
签名、非对称加密和密钥交换解决不同问题,不要用“公钥加密、私钥解密”一句话概括全部非对称密码学。
第六组:密钥交换、证书与高级接口
DiffieHellman / ECDH
X509Certificate
checkPrime() / generatePrime()
getCiphers() / getHashes() / getCurves()
FIPS 相关接口
它们常用于 TLS、协议实现、证书检查和特定合规环境。普通 CRUD 服务很少直接操作这些底层接口。需要时应先理解目标协议,不建议为了“学全 API”自行设计网络加密协议。
0.4 官方接口有三种常见形状
形状一:创建对象,再分段处理
import { createHash } from "node:crypto";
const hash = createHash("sha256");
hash.update("hello ", "utf8");
hash.update("world", "utf8");
const result = hash.digest("hex");
Hash、Hmac、Cipheriv、Decipheriv、Sign、Verify 都有类似的“创建 → 输入数据 → 完成”生命周期。
create...() -> update() 一次或多次 -> digest()/final()/sign()/verify()
完成方法调用后,对象通常不能重新开始另一份独立计算。处理新消息时应创建新对象。
形状二:一次性函数
import { randomBytes, timingSafeEqual } from "node:crypto";
const bytes = randomBytes(32);
const equal = timingSafeEqual(bytes, Buffer.from(bytes));
输入一次给全,直接取得结果,适合小数据和单一动作。
形状三:异步回调与同步版本
import { scrypt, scryptSync } from "node:crypto";
scrypt("password", "salt", 64, (error, derivedKey) => {
if (error) {
throw error;
}
console.log(derivedKey);
});
const derivedKey = scryptSync("password", "salt", 64);
带 Sync 的版本会阻塞当前 JavaScript 线程,直到计算完成。对于服务端请求中的耗时操作,通常优先使用异步版本,避免直接卡住事件循环。
异步不代表不消耗资源。scrypt()、pbkdf2()、异步随机数和密钥生成等工作仍会占用 CPU 或 libuv 线程池,因此需要限制并发并关注延迟。
0.5 Hash、Hmac 和 Cipheriv 为什么能处理大数据
这些对象可以多次调用 update(),不用要求所有数据一次性进入内存。部分对象还采用 Node.js Stream 接口,可以与文件流组合。
文件块 1 ─┐
文件块 2 ─┼─> Hash 持续更新内部状态 ─> 最终摘要
文件块 3 ─┘
哈希输出长度固定,但这不意味着 Hash 对象保存了完整输入。它维护的是算法内部状态;读取完所有块后,digest() 生成最终摘要。
加密流则不同,它会陆续产生密文字节。最后必须调用 final(),因为算法可能还有尾部状态需要完成;使用 GCM 时还要保存认证标签。
0.6 算法名为什么是字符串
很多接口这样调用:
createHash("sha256");
createCipheriv("aes-256-gcm", key, iv);
可用算法与当前 Node.js 链接的 OpenSSL、Node.js 版本和构建配置有关。官方提供 getHashes()、getCiphers() 等枚举接口,但“本机存在”不等于“适合新项目使用”。兼容旧算法和安全推荐是两件事。
教程选择 SHA-256、HMAC-SHA-256、scrypt、AES-256-GCM 和 Ed25519,是为了覆盖常见现代用途。实际系统还应服从外部协议规范、合规要求和当前安全建议。
0.7 版本是 API 的一部分
官方最新版页面会展示最新 Node.js 的能力,而项目真正能运行什么由以下因素共同决定:
实际 node --version
+
TypeScript 的 @types/node 版本
+
Node.js 构建时的 OpenSSL/crypto 支持
↓
项目实际可用接口
当前仓库声明了 @types/node ^20.11.0,所以本教程主体采用 Node.js 20 能理解和运行的接口。当前终端的 node --version 是 v12.22.1,运行前需要切换到 Node.js 20+,使运行时和类型声明对齐。
检查方式:
node --version
npx tsc --noEmit
如果从官方最新版文档复制代码,还应打开对应 API 的 History Version Changes,确认它从哪个 Node.js 版本开始提供,以及当前 Stability 状态。