【免费下载链接】breakscale
Build a system, load it until it breaks, and watch why. A system design simulator for learning distributed systems.
在 Breakscale 中,你画下的每个架构设计都可以一键变成链接发给别人——但设计内容是否会被服务器读到?答案是:不会。本文解析 Breakscale 分享链接的两套机制:AES-GCM 客户端加密与URL 片段(fragment)密钥分离,让你明白为什么哪怕链接存储库被拖库,对方也拿不到你的架构。
两种分享方式:先看全貌
Breakscale 提供两种把设计变成链接的方式,安全性各有侧重:
| 方式 | 链接形态 | 设计存放在哪 | 适用场景 |
|---|---|---|---|
| 纯片段链接 | …#d3.…… | URL 的#之后 | 小设计、离线可用、永久有效 |
| 加密存储链接 | …?s=<id>#k=<key> | 云端存储(密文) | 大设计、短链接 |
关键区别在于链接长度的代价:纯片段链接把整个设计塞进 URL,超过约 2000 字符后,Discord、Slack 或邮件客户端就可能截断它;而存储链接无论设计多大,URL 始终只有约 60 个字符。
核心原理:密钥分离为什么有效
先理解浏览器的一条基础规则:#之后的 fragment 永远不会被发送到服务器。它只存在于用户的浏览器里,只有用户主动粘贴时才"旅行"。
Breakscale 把加密链接拆成两半,分别走两条路线:
- 查询参数
?s=<id>:服务器看得到,但它只是一个不透明的 ID; - 片段
#k=<key>:服务器永远看不到,但它是打开密文的钥匙。
于是存储端拿到的是"一把锁着的箱子 + 一个取件码",唯独没有钥匙。整个设计的核心可以一句话概括:
存储持有密文和命名它的 id,但在任何时刻都不持有能打开它的密钥。
加密逻辑位于 src/share/crypto.ts,密钥拼装成最终链接的实现在 src/share/store.ts。
AES-GCM 加密细节:为什么选它
打开 src/share/crypto.ts,可以看到几个耐人寻味的选型决策:
为什么是 AES-GCM 而不是 AES-CBC
GCM 模式一边加密一边做完整性认证。被篡改的密文——无论是在存储里、传输途中,还是被人手抖敲错一个字符——会直接解密失败,而不是解出一堆"看起来像那么回事的垃圾数据"再靠下游兜底。
为什么密钥是 128 位而不是 256 位
因为钥匙本身就挂在 URL 上,会被粘贴进聊天窗口。128 位密钥编码成 base64url 只有 22 个字符,256 位则要 43 个。128 位 AES 目前没有实际可行的破解路径,而这里真正防的威胁是"存储被攻破",不是"有人对着你这张图算十年"。
IV 的处理:12 字节,每次新生成
GCM 模式规定 IV 为 12 字节(IV_BYTES 定义)。它不是秘密,但绝不能与同一把密钥复用——而 Breakscale 为每个设计单独生成一把密钥,天然杜绝了复用问题。这也是刻意不用"一个浏览器一把钥匙"的原因:共享链接会把钥匙交给每个持有 URL 的人,一把钥匙覆盖多个设计,等于把别人的设计也一并送出。
打开与加密的完整链路
发送端(storeTopology):
- 把设计压缩打包成紧凑字节流;
- 生成一次性密钥,用 AES-GCM 加密,拼出
IV + 密文的 blob; - 只把 blob 上传到存储,拿到
id; - 把密钥编码进
#k=,组装出最终链接——密钥从未出现在上传请求里。
接收端(fetchStored):
- 从 URL 片段取出密钥(浏览器本地就能读到);
- 凭
?s=<id>拉取密文 blob; - 在本地浏览器中解密——明文从未离开过持有链接的人的环境;
- 解出的字节走与纯片段链接完全相同的解码和校验路径,一个存储的设计不会比一个粘贴的设计多获得一分信任。
存储端为什么"构造上"读不到设计
链接存储是一个极简的 Cloudflare Worker:worker/src/index.ts 只做两件事——POST /v1存入密文并返回随机 ID,GET /v1/:id取回。
它"不能"做的事情,是设计出来的:
- 读不了:每个 blob 都带着只存在于发送者浏览器里的密钥;
- 搜不了:它不知道里面是什么,无法按内容检索;
- 发不了:没拿到链接的人拿到 ID 也只是一堆无意义的字节。代码注释里说得很直白:" Losing the whole store would leak nothing. "(整个存储丢了也什么都泄露不了。)
正因为存储连"这是不是架构图"都分不清,防御只能做在形状上而非内容上:大小上限 256KB(MAX_BLOB_BYTES)、按 IP 的写入速率限制(429)、ID 由服务端随机生成(80 bit,穷举撞中概率极低)、写操作做 Origin 白名单校验。
解码侧的防御纵深
即使拿到了密文和密钥,恶意构造的 URL 依然过不了客户端的层层关卡,逻辑集中在 src/share.ts:
- 版本前缀
d3.:格式可被"检测"而不是"猜",旧版链接(d1./d2.)的读取器保留至今,离线永久可开; - base64url 字母表校验:链接被聊天软件截断或转义污染时,解码器统一给出可读的报错,而不是崩溃;
- 解压炸弹防护:deflate 可以把几百字节膨胀成几百 MB,MAX_DECODED_BYTES 把输出上限钉在 2MB,超限就取消流而不是等它膨胀完;
- 结构校验 + 注释消毒:解出的拓扑走与粘贴板完全相同的
isTopology校验,注释还要过sanitizeAnnotations,防止用颜色值逃逸 style 属性注入样式。
一个细节值得留意:解码永远不会抛异常——截断、坏字母表、膨胀炸弹、恶意注释,全部收敛为同一句人类可读的提示,而不是白屏。
分享按钮的"刻意设计"
最后看一个容易被忽略的交互决策,它其实和安全模型直接相关:Share 对话框打开时不会生成链接,必须再点一次按钮才真正创建。原因有三:
- "打开对话框看看说明"不等于"同意上传设计"——写存储是设计中唯一离开浏览器的一刻;
- 存储的 blob不可撤销(无法"取消存储"),所以它应当跟随一次明确点击;
- 每次窥视都会消耗当日写入预算,为一条从没发出去的链接付费不值。
同样刻意为之的还有"不做"清单:没有过期时间选择器、没有"谁可以查看"、没有账号——因为存储根本读不懂设计,也认不出作者,任何这类配置都是空转的。
小结
| 机制 | 防住的威胁 |
|---|---|
| 密钥放 URL fragment | 服务器/存储看到设计明文 |
| AES-GCM 认证加密 | 密文被篡改后静默解码出错误内容 |
| 每设计一把密钥 | 一个链接泄露牵连其他设计 |
| 80 bit 随机 ID + 速率限制 | ID 被枚举遍历 |
| 2MB 解压上限 | 解压炸弹拖垮浏览器 |
| 统一的校验/消毒器 | 恶意拓扑与样式注入 |
下次在 Breakscale 点下"分享"时,你可以有把握地说:这条链接里的架构,只属于把它粘贴出来的人。想动手验证细节,推荐从 src/share/ 目录开始读起——加密(crypto.ts)、存储(store.ts)与链接格式(wire.ts)各占一个文件,注释写得像设计文档,非常值得一读。
【免费下载链接】breakscale
Build a system, load it until it breaks, and watch why. A system design simulator for learning distributed systems.
相关推荐
Plannotator 计划分享与协作指南:URL 片段压缩分享、AES-256-GCM 加密短链与隐私模型
Plannotator 计划分享与协作指南:URL 片段压缩分享、AES 256 GCM 加密短链与隐私模型 Plannotator 通过 URL 把「计划(P
SeaTunnel FieldEncrypt 加密 Transform 插件实战指南:AES-GCM/AES-CBC 字段加解密原理与配置
SeaTunnel FieldEncrypt 加密 Transform 插件实战指南:AES GCM/AES CBC 字段加解密原理与配置 SeaTunnel
数据集成ETL大数据批处理流处理变更数据捕获Aegis Vault 安全设计与文件格式完全解析:AES-256-GCM 加密、scrypt 密钥派生与 JSON 存储结构
Aegis Vault 安全设计与文件格式完全解析:AES 256 GCM 加密、scrypt 密钥派生与 JSON 存储结构 Aegis 是一款用于管理两步验
移动开发安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考