不用后端鉴权,也能给静态站点加密:StaticShield 的设计与用法

静态站点的内容保护是个尴尬的命题。

GitHub Pages、对象存储、CDN——纯静态托管便宜到几乎免费,可它们没有服务端。「想给某个页面加密码、只让特定人看」这个再普通不过的需求,通常意味着得引入后端:一份鉴权服务、一个登录态、可能还有数据库。为了一个密码框,把无状态的静态站点拖回有状态架构,不划算。

StaticShield 换了个思路:把加密下沉到产物本身。用密码把 HTML 加密成一份「自解密页面」,密文与解密逻辑都内嵌在同一个 .html 里;部署照旧是纯静态,访客输入密码后由浏览器端 WebCrypto 实时还原。全程零后端。

主打:Web GUI 加密(本地后端)

日常加密走图形界面。--gui 会拉起一个仅绑定 127.0.0.1 的本地服务并打开浏览器:

1
node encrypt.js --gui

架构上是「本地 Node 后端 + 浏览器前端」,这么设计有一个关键原因:打包多文件网页需要文件系统访问,而浏览器在 file:// 下出于安全拿不到相对路径资源。所以加密放在本地 Node 端做(能读目录、能 bundle),前端只负责交互与上传——文件不离开本机

两种模式:

  • 单文件:选一个 HTML → 加密下载。
  • 整个文件夹:用 webkitdirectory 选网页根目录(index.html + css/ + js/ + 图片),后端先内联打包再加密,等效于 CLI 的 --bundle

底层是 Node http + 异步 WebCrypto(跑在 libuv 线程池),多个标签、多台设备同时访问可并行加密,互不阻塞;每请求独立临时目录、无全局可变状态。

本地服务的安全细节也做了:仅监听 127.0.0.1、Host 头校验(防 DNS rebinding 跨域读取响应)、Content-Type 校验、上传路径沙箱(防 ../../ 逃逸)、请求体大小上限与不活动超时。

加密管线:AES-256 + PBKDF2 + HMAC

不是 base64 藏一下,是正经的对称加密组合(Encrypt-then-MAC):

  1. 密钥派生PBKDF2(password, salt, 1,000,000) → 64 字节,前 32 字节作 encKey、后 32 字节作 macKey,加密与签名密钥隔离。
  2. 加密AES-256-CBC(随机 IV)加密明文 HTML。
  3. 签名HMAC-SHA256(macKey, AAD ‖ iv ‖ salt ‖ ct),其中 AAD 把 cipher / kdf / iter 等影响派生的元数据一并纳入——篡改迭代次数降级、或改成天文数字耗尽 CPU 的攻击,会直接验签失败
  4. 解密:先验签、后解密。验签不过即统一报错,不区分「密码错」与「文件损坏」,既避免信息泄露,也消除了 padding oracle 攻击面。

加密端(CLI / GUI 后端)与解密端(浏览器)共用同一份 crypto-core.js——基于 globalThis.crypto.subtle同构模块,Node 18+ 与浏览器跑同一份代码,产物格式一致、单点维护。

多文件网页:内联打包

--bundle(或 GUI「整个文件夹」)做的事,是把整站压成单个自包含 HTML 再加密:

  • <link rel=stylesheet><style>,递归内联 css 内 @importurl()
  • <script src> → 内联 <script>严格保持原加载顺序(依赖序不能乱)
  • <img / audio / video>.srcpostersrcset、favicon → data: URI
  • http(s) / CDN 绝对 URL 原样保留;相对路径沙箱约束在项目目录树内,防路径遍历

打包后仍是单 HTML,解密端无需任何改动。

CLI:给开发者的选项

GUI 适合一次性操作;需要脚本化、批量、或接进 CI/CD 时,CLI 更顺手:

1
2
3
4
5
6
7
8
# 打包整站 + 生成 16 位强密码 + 指定输出目录
node encrypt.js index.html --bundle --gen-pwd 16 -d ./dist

# 批量加密 + SHA-512 + 记住我 7 天
node encrypt.js internal/*.html --sha512 --remember 7

# 非交互:管道喂密码(CI 友好)
printf '%s' "$PASSWORD" | node encrypt.js secret.html

参数覆盖:--bundle(打包)、--gen-pwd [n](CSPRNG 生成强密码,拒绝采样消除模偏差)、--share(生成 #pwd= 自动解密链接)、--sha512--remember <days>--hint--logo-d 输出目录。-p 缺省则交互式提示输入(不回显,且正确处理 UTF-8 多字节密码的退格)。

部署:纯静态,随便放

产物是单个 .html,任何静态托管都行——GitHub Pages、Vercel / Netlify、OSS / COS / S3、Nginx,甚至直接发文件双击打开。无需运行时、无需数据库。唯一要注意:WebCrypto 仅在安全上下文可用(https://localhostfile://),纯 HTTP 部署会提示需 HTTPS。


安全边界得说清楚:浏览器端解密意味着密文会下发到客户端,StaticShield 属「门禁式」访问控制(防直接查看 / 抓取 / 爬虫),不构成对资深逆向者的绝对防护。但作为挡住绝大多数随意访问的一道门,成本几乎为零——零后端、零运行时依赖、一个文件部署。

适合付费内容预览、内部文档分享、临时私密页面。

👉 GitHub 项目地址 →