静态博客用构建期加密发布受保护文章
静态博客的发布物是文件。文章列表、文章正文、图片资源最终都会变成可以被 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 辅助生成,可能存在错误或遗漏,请以实际资料和官方文档为准。