【AIGC】没人砌的那堵墙,砌好了:点击即用的 shamir 文件分片上线

上一篇《找遍了全网的 shamir 方案,没有我要的"点击即用"的版本》的结尾是这么写的:

砖头齐了,墙没人砌。……如果你也想要一个"点击即用"的 shamir 文件小工具,欢迎留言,一起把它砌起来。

留言就不用了,墙我直接砌完了。

地址:https://tdstud1o.github.io/tool/shamir/,开源在 tdstud1o/tool。任意文件进,n 个分片 + 1 个密文出;凑齐任意 k 个分片,文件原样回来。纯前端、零后端、整页单文件。

一、先说"点击即用"到底长什么样

拆分侧,三步:

  1. 把文件(可多选)拖进页面,每个文件自动成为一层;
  2. 给每层定"需要几个人才能解锁"(k),高级操作里还有密钥串对齐、压缩、OTS 存证几个开关;
  3. 点「拆分」,下载 1 个密文 .tscf + n 个分片 _shareN.txt,n 大了自动打包成 zip。

恢复侧,把密文拖进去,页面会告诉你"这一层需要几片";

把分片一条条贴进来(粘贴文本、选文件都行),够 k 片点「恢复」,文件落地,SHA-256 自动校验。

分片 是什么?就是相当于你把一个密钥掰成5份,交给5个朋友,但是只需要3个分片就能解开这个文件。

像这样,缺少解不开。

再多一片,解开了,还能下载。但是下一层解不开,提示还缺一片。没错,这里是我煞费苦心做的分层机制,像打怪探索一样,每打一层,就能一窥距离下一层还需要多少片密钥。

再多一片,原来是假的,白解了。

页面本身就是一个 420 KB 的 HTML 文件:CSS、JS、zstd 的 WASM、Worker,全内联。你可以把它另存到 U 盘,断网双击照常用。不开 F12 也能把整个源码读一遍——对一个隐私工具来说,“能被审计"是功能,不是彩蛋。

二、分片长什么样

一条分片就是一行文本:

TSK1:04829173:10000:y₁,y₂,y₃,y₄,20000,y₆…

拆开看:TSK1 是格式头,04829173 是这个人的坐标 x(一片只有一个),冒号后面是密钥材料,每组 44 字符。每片不管 n 多大,基本就是这一行——10 万人的局,每人保管的还是几十个字符的文本,打印在纸上、存成二维码、贴进备忘录,都装得下。

注意 10000 和 20000 这两个数字:这是"显示k”。每一层可以单独决定要不要把"这层需要几片"写进分片,把首尾两层勾上,参与者看到的视角就是——

1 万 - ? - ? - ? - 2 万

你知道自己站在哪、终点在哪,中间要凑多少片,只有解开上一层的人才知道。这个格式是给"活动"设计的:给小群里每人发一片,凑齐才揭晓,谁也不知道整局一共几个人。

三、为什么不抄现成的砖:自己写了一个 Shamir

上一篇提过,现成的底层库很成熟。真上手的时候发现两块砖都不能直接用:

  • secrets.js / privy-io 那一路是 GF(2⁸):多项式系数是单字节,n 上限 255。对助记词分片够用,对"给一万个人发分片"是笑话。
  • 换到素数域 GF(p),又有一个坑:秘密要先归约进域。常用 p = 2²⁵⁵−19 会让约一半的随机 32 字节密钥落在域外,做 mod p 归约不是单射——两个相差 p 的密钥会塌成同一个域元素,恢复只给出其中一个,另一个文件永久打不开,而且你不会收到任何报错。

所以我选了 secp256k1 的基域 GF(2²⁵⁶ − 2³² − 977):这个 p 离 2²⁵⁶ 只差 2³²+977,任意 32 字节密钥落在域外的概率约 2⁻²²⁴(实测 200 万次采样零命中),密钥原样当域元素用,一行归约代码都不需要。碰上了怎么办?丢弃重取,绝不取模。BigInt 手写,零依赖,n 上限 16,777,215。

代价是算得慢:k=10000 时一次多项式求值 3.23 ms,n=100000 的拆分要跑 5 分半。所以整个 Shamir 运算扔进了 Web Worker,主线程只动进度条,界面不会被算死;分片列表超过 50 行自动折叠,超过 20 份自动打包 zip(条目过 65535 写 ZIP64),10 万片也能一次下载。

四、给隐私执念者的细节

上一篇说过,会用这类工具的人对隐私普遍执念很深。所以除了"把文件加密、把密钥分片"这个主干,这个工具在"不泄露“这件事上花的力气比主干还多。明文侧——也就是不凑齐分片也能看到的全部内容——只有三样东西:阈值 k、12 字节随机 IV、密文。没有文件名,没有大小,没有时间戳,没有层数。

几个有意思的设计:

多层嵌套,层是套娃。 一个密文里可以套任意多层,每层一个文件、各有一个 k。凑够 3 片解出第 1 层,此时才看得到第 2 层密文的字节;凑够 4 片才轮到它。关键是"链指针"藏在内层、跟着载荷一起被加密——要是放在明文侧,拿到密文的人不用密钥就能顺着块头数出"这套一共几层、每层多大”,那层数本身就成元数据了。放内层之后,明文侧连"有几层"都看不出来。

“隐藏 n"和定宽坐标。 单层的 n 写在明文里,方便恢复端判断;多层一律不写。多层(或勾了隐藏 n)时,分片的 x 在 1 到 1677 万里随机取、固定 8 位补零——不然 x 的位数就把 n 的量级卖了:1 位数 → 不到 10 个人,5 位数 → 几万人。

假文件层。 只能加一层、必定垫底。它的字节结构和真文件一模一样,但拆分时"分片共享的密钥"和"加密用的密钥"是两把独立的随机密钥——所以凑够它的 k 片,插出来的是一把打不开任何东西的随机密钥,AES-GCM 认证失败。它的 k 是唯一允许大于 n 的:设成 n+1,连"凑够"这个动作本身都不存在。配合密钥串对齐(一片固定补齐 32 或 256 组密钥材料,多出来的全是无密文的随机组),拿走全部分片的人也分不清哪些组是真的、这套里面到底有几层。

OTS 存证的联网策略。 可选把文件哈希提交到 OpenTimestamps 公共日历,拿到"这个哈希在某个时刻已存在"的比特币锚定,回执也藏在密文里。默认不勾;一旦涉及联网(恢复时的自动查询),只在明确允许跨域的白名单日历上发生——而白名单目前是空的。也就是说:不勾存证,这个页面从头到尾一个网络请求都不会发。

五、还顺手做了的

  • 压缩:默认 zstd,对文件头 256 KiB 采样试压,收益不到 1%(jpg/zip/mp4 这类)就不压,省得白等;
  • 时间戳存证:上面说的 OTS,回执跟着密文走,恢复时离线比对、可选在线验证;
  • 暗色默认:加载即暗,没有白屏闪烁;
  • 命名四选项:日期前缀 / 文件名 / UUID / 自定义,记住你选过的。

六、生态位,填上了

回头对一下上一篇的验收标准:任意文件 ✓、混合加密(AES-256-GCM 加文件、Shamir 只分片 key)✓、n 分片 + 1 密文 ✓、纯前端 ✓、GitHub Pages 零后端 ✓、开源 ✓。

字符串分片有 iancoleman,密钥卡片有 QuorumPony,命令行有 fractum——现在”文件进、分片+密文出、点击即用“这个空了不知道多少年的位置,也有人站了。

打开 https://tdstud1o.github.io/tool/shamir/,拖一个文件进去试试。把游戏存档拆给两个朋友、给群友发"开盲盒"分片、把家庭相册的钥匙散给家人——这些上一篇里写的玩法,现在都有地方点了。

路线图上还有二维码分片、中英双语、移动端适配(M4),代码在 tdstud1o/tool,欢迎来提 issue。