@adep/crypto
v0.1.0
Published
云函数沙箱内可用的密码学工具集(HS256 JWT / PBKDF2 口令哈希 / AES-256-GCM),纯 WebCrypto 零依赖,替代 jsonwebtoken + bcrypt + node:crypto。
Readme
@adep/crypto — 云函数沙箱内可用的密码学工具集
JWT(HS256)/ 口令哈希(PBKDF2-SHA256)/ 对称加密(AES-256-GCM)。纯 WebCrypto、零依赖、零 Node API——adep 的云函数沙箱没有
node:*、也没有原生 addon,本包是把沙箱里已注入的 WebCrypto 封装成可直接用的形式。
npm install @adep/crypto
# 或
pnpm add @adep/crypto在 adep 平台上,本包已进沙箱内置依赖白名单(
@adep/[email protected]+/ 平台缺省配置),函数里直接import即可,无需装依赖。
它替代什么
| 本包 | 替代 | 为什么替代品在沙箱里跑不了 |
| --- | --- | --- |
| jwt.ts | jsonwebtoken | 依赖 node:crypto 的同步 HMAC |
| password.ts | bcrypt | 原生 C++ addon,沙箱无法加载 |
| aes.ts | node:crypto 的 createCipheriv | node:* 不可达 |
对从 Node / Nuxt / Workers 迁过来的项目,这三件事几乎必然用到;缺了它们,每个项目都要在自己的 _shared/ 里各写一份——同一算法多份拷贝必然静默分叉。
JWT
import { signJwt, verifyJwt, tryVerifyJwt, JwtError } from '@adep/crypto'
const token = await signJwt({ sub: 'user-1', role: 'admin' }, ctx.env.JWT_SECRET, {
expiresIn: '2h', // 也接受 expiresInSeconds;裸数字字符串('45')= 秒
})
try {
const claims = await verifyJwt<{ sub: string; role: string }>(token, ctx.env.JWT_SECRET)
// claims.sub / claims.role
} catch (error) {
// JwtError.code 区分 'expired' 与 'tampered' 等
if (error instanceof JwtError) return new Response(error.code, { status: 401 })
throw error
}
// 仅在「所有失败一律当未授权」时使用(失败返回 null,不抛)
const maybe = await tryVerifyJwt(token, ctx.env.JWT_SECRET)- HS256;签发与校验两侧都把
alg钉死,防alg: none与算法降级。 - 支持
exp/nbf/iat/iss/aud校验;clockToleranceSeconds放行时钟漂移(缺省 0)。 verifyJwt绝不返回 null——避免调用方漏判。
口令哈希
import { hashPassword, verifyPassword, isBcryptHash, needsRehash } from '@adep/crypto'
const stored = await hashPassword(password) // 'pbkdf2$sha256$210000$<salt>$<hash>'
const ok = await verifyPassword(password, stored)
// 存量 bcrypt 口令的兼容分支:校验通过后就地升级,不强制重置
if (isBcryptHash(stored) && (await verifyBcrypt(password, stored))) {
await updatePassword(userId, await hashPassword(password))
} else if (needsRehash(stored)) {
await updatePassword(userId, await hashPassword(password))
}- PBKDF2-SHA256,缺省 210k 迭代 / 16B salt / 32B key,参数随哈希串自描述(
HashPasswordOptions可调)。 - 校验走恒定时间比较;格式非法、拿到 bcrypt 哈希、算法不匹配一律返回
false。 - 空口令被拒绝——空口令哈希一旦落库,任何空输入都能通过校验。
对称加密
import { encryptSecret, decryptSecret } from '@adep/crypto'
const cipher = await encryptSecret('sk-live-xxxx', ctx.env.CRYPTO_SECRET)
const plain = await decryptSecret(cipher, ctx.env.CRYPTO_SECRET) // string | null- AES-256-GCM,密文格式
iv:authTag:ciphertext(base64),与 Node 侧createCipheriv实现逐字节兼容——迁移时既有密文无需重加密。 - 密钥由
deriveAesKey(secret)从口令串派生(SHA-256(secret),与 Node 版getEncryptionKey等价)。 decryptSecret按「未配置」语义返回null(空串 / 格式非法 / 认证失败——密钥轮换后旧密文即视为未配置,重存一次即可);需要区分「格式错」与「被篡改」时用decryptSecretStrict(任何失败都抛)。secretEquals(a, b)恒定时间比较两个密文串,供「值有没有变」的判断。- 同一密钥下绝不可复用 IV;本包每次加密随机取 IV。
编码工具
base64ToBytes · bytesToBase64 · base64UrlToBytes · bytesToBase64Url · base64UrlToUtf8 · utf8ToBase64Url · utf8ToBytes · bytesToUtf8 · timingSafeEqual · EMPTY_BYTES
说明
- 零运行时依赖;沙箱已注入
crypto/subtle,本包只是它的封装。 - 类型契约见
packages/crypto/dist/*.d.ts;实现见仓库packages/crypto/src/。
