零依赖Excel加密:hucre如何用WebCrypto实现Agile密码保护
【免费下载链接】hucreZero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Pure TypeScript, works everywhere.项目地址: https://gitcode.com/gh_mirrors/hu/hucre
hucre 是一个零依赖的电子表格引擎,能读取和写入 XLSX、CSV、ODS 等格式,全部用纯 TypeScript 编写。除了基础的读写能力,它还有一个常被忽视的硬核功能——Excel 文件加密:通过平台原生的 WebCrypto API 实现 ECMA-376Agile 密码保护(Excel 2010+ 采用的加密方案),在 Node、Deno、Bun、Cloudflare Workers 乃至浏览器里都能直接运行,不需要引入任何密码学第三方库。
什么是 Agile 加密?
当你用 Excel 给文件"设置密码"时,从 2010 版本起,Excel 默认使用的就是Agile 加密(定义于 ECMA-376 标准,细节由 [MS-OFFCRYPTO] 规范描述)。它的核心配方是:
- AES-256-CBC加密文件内容
- SHA-512哈希派生密钥,默认迭代100,000 次(称为 spin count)
- HMAC-SHA512校验数据完整性,防止密文被篡改
- 整体打包进OLE2/CFB 复合二进制容器(而不是普通的 ZIP)
hucre 对这一整套流程做了完整的加密与解密实现,源码集中在 src/xlsx/crypto/ 目录下的两个文件:
| 文件 | 职责 |
|---|---|
| src/xlsx/crypto/agile.ts | Agile 密码学的全部逻辑:密钥派生、AES-CBC 分段加解密、HMAC 校验 |
| src/xlsx/crypto/cfb.ts | 最小化的 OLE2/CFB 容器读写器 |
加密与解密:各一行 API
对使用者来说,Agile 加密被封装得非常轻——写文件时多传一个encryption选项,读文件时多传一个password选项,就完事了:
import { writeXlsx, readXlsx } from "hucre" // 写一个带密码保护的 XLSX const encrypted = await writeXlsx({ sheets: [{ name: "Secret", rows: [["pin", 1234]] }], encryption: { password: "hunter2" }, }) // 带上密码读回来 const wb = await readXlsx(encrypted, { password: "hunter2" })这套选项在所有读取入口都生效:read()会自动探测容器类型并透明解密,readObjects()和streamXlsxRows()同样支持传password;往返编辑路径saveXlsx(wb, { encryption: { password } })也走同一个加密器(见 src/xlsx/roundtrip.ts)。
密钥是如何从密码中"变"出来的?
密码本身不能直接当密钥用。hucre 按 MS-OFFCRYPTO 规范实现了完整的派生链,核心在 passwordChain:
- 加盐:随机生成 16 字节的盐值,与 UTF-16LE 编码的密码拼接后做首次 SHA-512;
- 迭代拉伸:用 4 字节计数器反复哈希,默认循环 100,000 次(
spinCount),让暴力破解的成本线性放大——这就是为什么加密一个文件会有可感知的耗时; - 派生多组块密钥:从哈希链上再派生出 verifier 密钥、密钥加密密钥、数据密钥等,各司其职;
- 随机密钥包裹:真正的包密钥
secretKey是随机生成的 32 字节,用上面的派生密钥 AES-CBC 加密后存进EncryptionInfo元数据中。
解密时则反过来:用你输入的密码重走一遍派生链,解出随机验证输入和它的哈希值,两者对上才认为密码正确(见 deriveSecretKey),否则抛出DecryptionError。
密文怎么存?——分段 AES-CBC + 完整性校验
Excel 的文件体按4096 字节分段,每段用独立的 IV 做 AES-CBC 加密(IV 由盐值加段索引哈希而来,见 encryptPackage)。这里有个精巧的工程细节:WebCrypto 的 AES-CBC总是强制 PKCS#7 填充,而 OOXML 的分段是"裸"的块对齐数据。hucre 用两个经典技巧绕过它——解密时追加一个精心构造的密文块诱使 WebCrypto 把填充块剥掉,加密时则丢弃尾部填充块(代码开头的注释 agile.ts 解释得很清楚)。
此外,hucre 还会验证<dataIntegrity>元素里的 HMAC:解出 HMAC 密钥后对整条EncryptedPackage流计算 HMAC-SHA512,不匹配就拒绝输出(见 verifyDataIntegrity)。这意味着密文哪怕被篡改一个字节,也不会静默地给你一份损坏的数据。
安全细节:不只是"能加解密"
几个容易被忽略的防线,hucre 都做了:
- DoS 防护:
spinCount来自不受信任的文件。恶意文件可以谎报一个天文数字的迭代次数,把 CPU 钉死几分钟。hucre 设了上限 MAX_SPIN_COUNT = 10,000,000(Office 自己只用 10 万),超上限直接拒绝解密; - 零依赖:所有密码原语直接调用
crypto.subtle.digest、crypto.subtle.encrypt和crypto.getRandomValues(见 WebCrypto 原语层),没有任何 WASM 或纯 JS 的 AES 实现——这也是它能在 Cloudflare Workers 和浏览器里跑的原因; - 清晰的错误分层:忘了传密码抛 EncryptedFileError,密码错误或数据损坏抛 DecryptionError,两者语义分明,方便你在 UI 里分别提示"需要密码"和"密码不对";
- 兼容 Excel 默认参数:加密默认使用 Excel 原生的 100,000 次迭代,产出的文件可被 Excel 直接打开;测试里也有专门的用例验证这个默认值下的端到端互通(encryption.test.ts)。
验证:加密文件到底"长"什么样?
从 test/encryption.test.ts 可以看到完整的行为契约:
- 加密输出是 OLE2 容器而非 ZIP(
isOle2Container(enc)断言为 true); - 无密码读取 →
EncryptedFileError,错误密码 →DecryptionError; openXlsx+saveXlsx可以"换密码":用第一个密码打开、用第二个密码重新保存,旧密码随即失效;- CFB 容器读写器对小型流(mini-stream)和大型流都做了往返验证。
小结
hucre 把一套工业级的 Office 文件加密——OLE2 容器、AES-256-CBC 分段、SHA-512 密钥拉伸、HMAC 完整性校验——完整地装进了零依赖的 TypeScript 里,全部密码运算借力平台自带的 WebCrypto。对使用者的最终体现却极简:写时传一个password,读时传一个password。如果你的项目需要给电子表格加上真正的密码保护,又不想为此引入一堆二进制依赖,src/xlsx/crypto/ 值得仔细读一读。
【免费下载链接】hucreZero-dependency spreadsheet engine. Read & write XLSX, CSV, ODS. Pure TypeScript, works everywhere.项目地址: https://gitcode.com/gh_mirrors/hu/hucre
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考