静态博客用构建期加密发布受保护文章

静态博客的发布物是文件。文章列表、文章正文、图片资源最终都会变成可以被 HTTP 直接读取的静态内容。隐藏入口、删除导航、排除搜索索引,只能降低可发现性,不能保护内容本身。

受保护文章的设计目标很明确:公开站点仍然是纯静态部署,私密文章不进入公开索引,正文和图片以密文形式发布。读者只有拿到直达路径和密钥,才能在浏览器本地解密。

静态博客加密文章构建与访问流程

基本思路

把文章分成两类:

公开文章:生成公开索引 + 生成明文内容 JSON
受保护文章:跳过公开索引 + 生成加密内容 JSON

公开索引用于首页、分类、搜索和爬虫读取。受保护文章不写入索引,只保留可预测的直达路径:

/blog/{category}/{slug}

直达路径不包含密钥。密钥只由读者输入,浏览器用它解密对应的密文 JSON。

写作入口

作者仍然写普通 Markdown。唯一的区别是在 frontmatter 里放一个构建期密钥:

---
title: 'AES-GCM 加密文章直达访问测试'
coverText: '验证文章密文发布'
date: '2026-05-05'
author: 'createskyblue'
email: 'createskyblue@outlook.com'
tags: ['加密', 'AES-GCM', '测试']
coverImage: './cover.png'
encryptKey: 'replace-with-real-secret'
---

encryptKey 是构建期输入。构建脚本读取它后只用于派生加密密钥,不写入产物。构建结果里不能出现密钥、标题、正文、标签、图片文件名或图片 data URL 的明文。

构建期流水线

构建脚本扫描内容目录时先解析 frontmatter。没有密钥的文章按普通流程处理。有密钥的文章走加密流程:

读取 Markdown
解析 frontmatter
Markdown 渲染为 HTML
本地图片替换为 data URL
组装文章元数据和正文
用密钥派生 AES key
AES-GCM 加密完整文档
写入单篇密文 JSON
跳过 posts.json

核心设计是构建期分流。公开索引只记录公开文章。受保护文章不参与列表、分类计数和搜索。这样即使搜索接口、首页数据或爬虫直接读取 posts.json,也拿不到这篇文章的存在信息。

加密载荷

密钥不能直接作为 AES key 使用。构建端先生成随机 salt,再用 PBKDF2-SHA-256 派生 256 位密钥:

KDF: PBKDF2-SHA-256
Cipher: AES-256-GCM
Salt: 16 random bytes
IV: 12 random bytes
Tag: GCM authentication tag
Iterations: 310000

生成的 JSON 保存解密所需的公开参数:

{
  "encrypted": true,
  "version": 1,
  "algorithm": "AES-256-GCM",
  "kdf": "PBKDF2-SHA-256",
  "iterations": 310000,
  "salt": "...",
  "iv": "...",
  "ciphertext": "...",
  "tag": "..."
}

salt、IV、迭代次数和认证标签可以公开。需要保护的是密钥和明文。AES-GCM 是认证加密,密钥错误或密文被篡改时,解密会直接失败。

图片也必须进入密文

很多静态博客会把 Markdown 图片复制到公开资源目录。受保护文章不能这么做。正文加密但图片仍然明文发布,会泄漏文章主题、内容截图、图表和封面。

处理方式是把本地图片作为文章内容的一部分:

./image.png -> data:image/png;base64,...

替换后的 HTML 再整体加密。这样发布物中不会出现图片文件名,也不会出现 data:image 明文。读者解密成功后,浏览器直接渲染 data URL 图片。

coverImage 也按同样规则处理。受保护文章没有公开卡片,封面只在解密后的文章对象里存在。

访问时的解锁流程

访问文章路径时,客户端先查公开索引:

索引命中:加载普通内容 JSON,直接显示
索引未命中:创建受保护文章占位,尝试加载密文 JSON

密文 JSON 加载成功后,页面显示密钥输入框。用户输入密钥后,浏览器执行:

PBKDF2-SHA-256 派生 key
AES-GCM 解密 ciphertext + tag
JSON.parse 得到文章元数据和正文
渲染正文

解密失败时保持锁定状态。错误密钥、损坏密文、错误认证标签都走同一个失败分支。页面不需要知道失败原因,也不应该暴露更细的判定信息。

安全边界

这套设计解决的是静态发布物明文泄漏问题。攻击者可以下载密文 JSON,但没有密钥只能做离线猜测。PBKDF2 增加了猜测成本,真正的安全性仍取决于密钥强度。

适合使用的场景:

不希望进入首页和搜索的半私密文章
只分享给少数人的资料页
静态站点中需要保留纯前端部署的受保护内容

不适合替代的能力:

用户账号体系
服务端鉴权
访问日志
密钥撤销
下载限速
防止密钥持有者转发内容

密钥一旦泄漏,旧密文就失去保护。处理方式是更换 frontmatter 中的密钥并重新构建,让同一篇文章生成新的密文。

验证标准

构建后至少检查这些条件:

posts.json 搜不到受保护文章 slug
posts.json 搜不到受保护文章标题
posts.json 搜不到密钥和图片文件名
单篇 JSON 包含 "encrypted": true
单篇 JSON 包含 "algorithm": "AES-256-GCM"
单篇 JSON 搜不到正文片段
单篇 JSON 搜不到 data:image
公开图片目录不存在受保护文章图片副本

浏览器验证也要覆盖两条路径:

正确密钥:显示正文和图片
错误密钥:显示解密失败

结论

静态站点可以做真正的文章加密。核心思路是把“是否公开”前移到构建期:公开文章进入索引和明文内容文件,受保护文章跳过索引并生成认证加密载荷。图片作为正文的一部分进入密文。客户端只负责输入密钥、派生 key、解密和渲染。

这个方案不依赖具体前端框架。只要构建端能生成密文 JSON,浏览器端能使用 Web Crypto,就可以放进任何静态博客系统。

本文由 AI 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。