passvault-crypto
v0.1.0
Published
PassVault 零知识加密模块:PBKDF2 密钥派生 + TweetNaCl secretbox 认证加密,微信小程序与 H5 共用
Maintainers
Readme
passvault-crypto
密小本(PassVault) 的零知识加密核心——纯 JavaScript,无框架依赖,可在任意 JS 环境运行(浏览器 / uni-app / 小程序 / Node)。
零知识密码管理的全部密码学逻辑都集中在这个包里,代码量不足 200 行,欢迎任何人独立审计。
- 密钥派生:PBKDF2-SHA256,100,000 次迭代
- 数据加密:TweetNaCl secretbox(XSalsa20-Poly1305 认证加密)
- 运行时零依赖(crypto-js / tweetnacl 为依赖,均无内建后门能力——随机源可强制接管,见下文)
安装
npm install passvault-crypto(源码见 GitHub。)
API
| 函数 | 说明 |
|---|---|
| deriveMasterKey(masterPassword, salt, iterations) | 主密码 → masterKey(32 字节)。只在客户端调用,结果绝不离开内存 |
| deriveAuthHash(masterKey, username) | masterKey → 登录凭证 authHash(hex)。这是服务器唯一见过的「密码派生物」 |
| generateDek() | 生成每用户一把数据密钥 DEK(32 字节随机) |
| randomSalt() | 生成 16 字节 base64 盐 |
| wrapDek(dek, masterKey) | 用 masterKey 包裹 DEK(secretbox),返回 { wrappedDek, wrapNonce } |
| unwrapDek(wrappedDek, wrapNonce, masterKey) | 解包;主密码错误返回 null(认证标签校验失败),不抛异常 |
| encryptItem(item, dek) | 条目 JSON → { ciphertext, nonce }(整条认证加密) |
| decryptItem(ciphertext, nonce, dek) | 解密;篡改/密钥不匹配返回 null |
| installSecureRandom(fetchBytesAsync) | 强制接管随机源,返回 ensureRandom(bytes) |
密钥链
主密码(只存在用户设备内存)
│ PBKDF2-SHA256(密码, salt, 10万次)
▼
masterKey ──HMAC-SHA256──► authHash(上服务器,服务器再 bcrypt 后存库)
│
└─ 解开 wrappedDek ──► DEK ──secretbox──► 条目密文(上服务器)- 服务器只见:authHash(bcrypt 后)、wrappedDek、条目密文
- 改主密码 = 用新 masterKey 重包裹 DEK,历史数据零重加密
- 忘记主密码 = 数据永久不可找回(这是设计,不是缺陷)
如何审计我们没有留后门
- 读完全部源码:
src/index.js一个文件,密码学路径只有派生(PBKDF2)、认证(HMAC)、加密(secretbox)、随机数(nacl.randomBytes)四种原语 - 检查随机源:包内唯一可能引入后门的位置是随机数。
installSecureRandom强制替换 nacl 的 PRNG 为你提供的安全源——没有用系统随机源的默认路径,也就没有"悄悄换随机数"的空间(这也是为什么小程序/APP 端必须调用它,见下) - 验证认证加密:
decryptItem/unwrapDek对任何篡改返回null(Poly1305 标签校验),不存在"密文可被改写"的路径 - 跑测试:
npm test(node --test,覆盖派生⇄验证、包裹⇄解包、加密⇄解密、篡改拒绝、随机池)
随机源红线(接入方必读)
加密的安全性 = 随机源的安全性。tweetnacl 默认退化为 Math.random(不安全)时不会报错,只会静默产出弱密钥。
- 小程序 / APP:必须在运行时调用
installSecureRandom(你的安全随机获取函数),且每次generateDek/randomSalt/wrapDek/encryptItem之前await ensureRandom(...)——随机池是消费式的,池空时 nacl 会抛错提醒 - H5 / Node:无需接管(
crypto.getRandomValues自动生效)
import { installSecureRandom, generateDek } from 'passvault-crypto';
const ensureRandom = installSecureRandom(async (n) => {
// 小程序示例:从 uni.getRandomValues 取 n 字节的真随机数
// 实现见密小本 app 端 random.js
});
await ensureRandom();
const dek = generateDek(); // 安全