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,也接受多种字节视图 常用 ArrayBufferTypedArray
调用风格 对象、流、回调、同步和一次性函数并存 主要是 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()

这些名字都与“派生出一段密钥材料”有关,但用途不能互换:

最新版官方文档可能还有内置 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");

HashHmacCipherivDecipherivSignVerify 都有类似的“创建 → 输入数据 → 完成”生命周期。

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 HashHmacCipheriv 为什么能处理大数据

这些对象可以多次调用 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 状态。