sci-secure · v1.1.0

理科安全 从单文件离线应用里剥离出的加密模块。零依赖、零 DOM、可跨语言复现。

PBKDF2 600,000 次 AES-256-GCM + AAD Web Crypto · 零依赖 跨语言格式规范 MIT

这是什么

「理科计算器·验证器」是一份约 950 KB 的单 HTML 文件应用,加密逻辑与计算逻辑混在一起,谁都用不了、也没法单独审。这里把密码学部分完整抽出来,重写成两个不依赖宿主代码的包——一个纯算法层,一个带会话生命周期的完整安全库。

sci-secure-core

纯算法函数集。无状态,函数式调用,只管加解密。

sci-secure-lib

SecureVault 类。内置登录、锁定、会话密钥生命周期与可插拔存储。

还嫌麻烦?core 打包产物 / lib 打包产物 直接 npm install ./xxx.tgz。

密码学设计

环节算法 / 参数
口令派生PBKDF2-HMAC-SHA256,600,000 次迭代(OWASP 2023 建议值)
密钥分离HKDF-SHA256,按用途分域(会话 / 文件 v3 / v4 / v5 / TOTP / Argon2id)
对称加密AES-256-GCM,96 位 IV,128 位认证标签,元数据走 AAD 认证
会话密钥不可导出的 CryptoKey,JS 拿不到原始字节
校验口令与加密密钥分离派生,验证值不可反推密钥
可选 KDFArgon2id(内存密集,抗 GPU/ASIC),需宿主注入 WASM 实现

信封是自描述的

iterations、salt、iv、version 都写在密文头部,所以升级 KDF 强度不会让旧数据失效——旧信封按它自己记录的迭代数解开,新信封用新强度。

版本密钥来源说明
v2口令直派生最早期格式,仍可解
v3口令派生 + HKDF引入文件密钥分域
v4会话密钥 + AAD元数据参与认证,防篡改
v5Argon2id内存密集,需注入实现

快速开始

// 纯算法层
import * as sci from './core/sci-secure-core.js'

const env  = await sci.encryptWithPassword({ name: '张三' }, '我的密码')
const back = await sci.decryptWithPassword(env, '我的密码')

// 完整安全库
import { SecureVault } from './lib/index.js'
const vault = new SecureVault()
await vault.register('alice', '密码123')
const blob = await vault.encrypt({ note: '机密内容' })
vault.lock()

用别的语言解开

密文结构写在 FORMAT-SPEC.md 里,附实测向量;reference-impl-python.py 是照规范写的 Python 实现,已经和 JS 版互相解通过。也就是说,数据不锁死在浏览器里。

安全边界(请务必读)

这套东西保护的是拿到密文的人,不是拿到你设备的人。下面这些它做不到:

  1. JS 字符串清不掉。zeroize() 只能清 Uint8Array;口令一旦变成字符串就交给 GC 了。语言限制,不是偷懒。
  2. 本机被控则无解。内存里有明文和会话密钥,扩展、注入脚本、调试端口都能读。
  3. 登录退避可绕过。退避记录在 localStorage,清掉或直接对着密文爆破即可跳过;真正的下限由 KDF 强度决定。
  4. Argon2id 默认没生效。WebCrypto 没有 Argon2,必须自己注入 WASM 实现,否则走 PBKDF2。
  5. 口令强度仍是最弱一环。迭代数翻倍只是把单次猜测成本提高约一倍,弱口令照样被字典打穿。
  6. 需要安全上下文。crypto.subtle 只在 HTTPS 或 localhost 下可用。

把一个加密模块公开,不会削弱它——安全性建立在密钥和口令上,不在代码保密上(Kerckhoffs 原则)。公开的价值是能被审查,而不是让实现烂在文件里。这个仓库里没有任何私钥、口令或真实用户数据。

版本

版本变更
1.1.0PBKDF2 默认 310,000 → 600,000(向后兼容);新增 v5/Argon2id 信封、防钓鱼安全短语、zeroize;lib 增加登录退避、自动锁定、disposeTotp、Trusted Types 策略
1.0.0从主程序剥离,core 与 lib 双包成型