Files.md令牌系统深度解析:TOKENS_SALT与sha256如何保障同步安全
【免费下载链接】files.md🌱 Private, quiet space for thinking. Simple app for .md files.项目地址: https://gitcode.com/GitHub_Trending/fi/files.md
Files.md 是一款专注.md文件同步的轻量应用,主打"私密、安静的思考空间"。它的多端同步能力背后,是一套简洁却严谨的令牌认证系统:通过TOKENS_SALT加盐的 SHA-256 哈希存储、一次性令牌(one-time token)交换与 IP 封禁机制,在几乎没有数据库的情况下守护你的笔记安全。本文将带你从源码视角读懂这套设计。
为什么 Files.md 需要令牌机制
Files.md 的核心场景是:你在浏览器 PWA 中编辑 markdown 笔记,服务器负责在多设备间同步文件。任何一次同步请求都必须先证明"我是这个用户"。
传统做法是用户名密码 + 数据库会话,但 Files.md 走了一条极简路线:令牌即身份,哈希文件名即数据库。整个认证逻辑集中在 tokens.go 中,不到 300 行代码就完成了全部鉴权。
令牌生成:32字节真随机数
所有令牌都由genToken()生成:
- 从
crypto/rand(操作系统级加密随机源)读取32 字节熵 - 转为 64 位十六进制字符串作为令牌
32 字节意味着 256 位密钥空间——暴力穷举在物理上不可行。这是整个安全体系的第一块基石:令牌本身无法被猜测。
核心机密:TOKENS_SALT 与 sha256 加盐存储
这是本文的主角。永久令牌从不以明文落盘,服务器执行的关键操作在hashToken()中:
对
token + TokensSalt拼接后做 SHA-256 哈希,用哈希值的十六进制串作为文件名,文件内容只存用户 ID。
也就是说,磁盘上的令牌"数据库"长这样:
/opt/files.md/tokens/<sha256(token + salt)>.md→ 内容为一串用户 ID
盐(TOKENS_SALT)在这里起三个作用:
- 防止离线爆破:令牌目录一旦泄露,攻击者拿到的是哈希文件名。由于令牌是 32 字节真随机数,即便算出对应明文也无意义——但加盐后,攻击者连"这个文件名对应什么哈希输入"都无法跨服务器迁移复现
- 防止彩虹表:不同服务器使用不同的
TOKENS_SALT(通过环境变量注入,见 config.go),同样的令牌在不同实例上产生完全不同的文件名,无法预计算 - 隔离多实例:官方文档 docs/e2e-tests.md 明确说明了这一机制——令牌文件按
sha256(标识 + salt)命名
源码注释里还有一句硬核的量化说明:按每秒 10 亿次 SHA-256 哈希的速率,暴力破解需要约 10^60 年(见 tokens.go)。
一次性令牌:Bot 链接设备的"入场券"
Files.md 配有一个 Telegram Bot,你可以直接在聊天中记录想法。要打通 Bot 与 Web 应用,需要设备绑定:
- 在 Bot 中发送
/app,服务端调用GenOneTimeToken()生成一次性令牌,并拼出?token=xxx的 Web 链接(bot.go) - 该令牌仅存于内存(
oneTimeTokensmap),有效期只有10 分钟 - 浏览器打开链接后,前端把令牌 POST 到
/issuePermanentToken接口,服务端校验后立即从内存删除——令牌一次性使用,无法重放(app.js) - 交换成功后,服务器签发永久令牌并写入 HttpOnly Cookie
一次性令牌"短命 + 内存 + 用后即焚",永久令牌"长寿 + 落盘 + 哈希化",两者职责分离,是教科书级的设计。
永久令牌:HttpOnly Cookie 守护 10 年
交换成功后签发的永久令牌写入 Cookie,配置十分讲究(tokens.go):
HttpOnly: true—— JavaScript 无法读取,XSS 偷不走令牌Secure: true—— 仅 HTTPS 传输SameSite: None—— 支持 api 域与 app 域分离的双域部署MaxAge约10 年—— 基本"一次绑定,长期有效"
每次请求经过tokenMiddleware中间件:先查 Cookie(兼容旧的 Authorization 头),用findUserID()按sha256(token + salt)文件名读盘,拿到用户 ID 后才放行同步请求。完整的同步时序可参阅 docs/sync-flow.md。
防御攻击者:IP 封禁与"只留指纹"的日志
Files.md 的令牌系统还有两层容易被忽略的防御:
🛡️ 无效令牌即封 IP:携带无效令牌的请求返回 401,同时该 IP 被拉黑10 分钟;期间再来的请求直接 429,不再触碰业务逻辑。这是对暴力试探的直接遏制。
🛡️ 日志永不记录令牌明文:调试日志 tokens_dbg.go 会把每次鉴权失败追加到/tmp/auth,但对令牌只做tokenFingerprint()处理——记录长度和 SHA-256 前 8 位。源码注释直白地解释:哪怕 8 个字符的前缀,也足以大幅收窄暴力破解空间,而指纹足以关联同一次攻击。日志本身不会成为新的泄露面。
自建服务器:如何正确配置 TOKENS_SALT
如果你打算自部署(详见 docs/your-own-server.md),盐值是必填项。官方推荐的生成方式:
make init_server host=user@example.com salt=$(head -c 32 /dev/urandom | base64)Makefile 会将该值注入 systemd 服务的Environment=TOKENS_SALT=$(salt)配置(见 Makefile),随后通过.env文件被 config.go 读取。三条部署建议:
- ✅ 盐值必须是高熵随机串,不要用密码、日期这类可猜测内容
- ✅ 盐值丢失 = 所有已签发令牌文件"失配",所有设备需重新绑定,务必备份
- ⚠️ 迁移服务器时,
tokens目录与盐值需一起迁移(docs/your-own-server.md 的迁移章节专门提到了打包tokens.tar.gz)
总结:小系统里的大智慧
Files.md 的令牌系统用不到 300 行 Go 代码回答了"无数据库如何安全认证"的问题:
| 设计点 | 实现 | 安全收益 |
|---|---|---|
| 令牌生成 | 32 字节crypto/rand | 256 位熵,不可猜测 |
| 落盘存储 | sha256(token + TOKENS_SALT)文件名 | 明文不落盘,防离线爆破与彩虹表 |
| 设备绑定 | 10 分钟一次性令牌,用后即焚 | 链接泄露窗口极短,不可重放 |
| Cookie | HttpOnly + Secure | 免疫 XSS 窃取 |
| 暴力防御 | 无效令牌封 IP 10 分钟 | 试探成本直线上升 |
| 日志审计 | 仅记录哈希指纹 | 日志不泄露秘密 |
它证明了一件事:安全不必靠复杂度堆砌。理解 server/sync/tokens.go 之后,你也能在自己的小项目里复刻这套"加盐哈希 + 一次性交换"的轻量认证方案。
【免费下载链接】files.md🌱 Private, quiet space for thinking. Simple app for .md files.项目地址: https://gitcode.com/GitHub_Trending/fi/files.md
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考