ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Ente 2of3 原理深度解析:Shamir 秘密共享、卡片份额格式与离线恢复机制

2026/9/10 21:48:30 拓冰建站 浏览量
Ente 2of3 原理深度解析:Shamir 秘密共享、卡片份额格式与离线恢复机制 Ente 2of3 原理深度解析Shamir 秘密共享、卡片份额格式与离线恢复机制【免费下载链接】ente End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente2of3 是 Ente 推出的免费端到端私有 Web 应用部署于 2of3.ente.com它把一个重要秘密拆成三张恢复卡片任意两张即可还原秘密单张卡片则什么都得不到。本文以 2of3 的官方原理文档为主体结合 web/apps/twoof3 下的开源源码逐层拆解其背后的 Shamir 秘密共享数学、卡片上份额的二进制格式、版本兼容策略、离线恢复 HTML 文件以及宽容的 QR 解码流程。读完本文你将能读懂一张 2of3 卡片上的每一个字节理解任意两卡恢复在 GF(256) 上如何成立并掌握如何审计、校验甚至自行实现一套恢复工具。Shamir 秘密共享速览2of3 的数学基础2of3 建立在 1979 年提出的经典密码学方案Shamir 秘密共享之上。它实现的是该方案最简单也最有趣的场景把一个秘密拆成三份份额任意两份即可重构原秘密而单独一份除透露秘密的字节长度外对内容一无所知。一条直线穿过两个点的直觉Shamir 方案的直觉可以用一条直线来理解。任取一条穿过 y 轴的直线y 轴截距就是你的秘密斜率则是一个用完即弃的随机数。随后取三个不同的 x 值例如 x 1、2、3计算直线上对应的 y 值每一对 (x, y) 就是一份份额。把任意一份份额直线上一个点交给某人他可以画出无数条经过该点的直线关于 y 轴截距一无所知把任意两份份额直线上两个点交给某人同时经过两点的直线有且仅有一条他能直接算出 y 轴截距也就是秘密。为什么必须在 GF(256) 上做算术如果直接在普通整数上执行上述运算会泄露信息例如大秘密产生的份额大小具有可预测性因此 Shamir 方案把算术放在有限域中进行。2of3 使用GF(256)含 256 个元素的有限域它的便利之处在于每一个字节都是合法元素。这让 2of3 可以逐字节拆分秘密且每份份额与秘密完全等长。在 2of3 使用的 (k2, n3) 方案中逐字节的数学可以完整写出来对每个秘密字节s2of3 选取一个均匀随机的字节r份额 1 存储sXORr份额 2 存储sXOR (r × 2 in GF(256))份额 3 存储sXOR (r × 3 in GF(256))。GF(256) 的乘法采用标准 AES 多项式0x11b。上述三份份额中的任意两份加上它们的份额编号即 x 值 1、2、3就足以逐字节还原s从而拼回完整秘密而单独一份只是sXOR某个随机字节与随机数据不可区分。源码 shamir.ts 中的splitSecret与这份数学一一对应常数GF_POLY 0x11b定义了域多项式gfMul用移位 按位异或实现 GF(256) 乘法拆分时用crypto.getRandomValues一次性生成与秘密 校验和等长的随机系数数组然后逐字节写入三份份额。与 Legacy Kit 的关系2of3 刻意只实现 2-of-3 这一种场景以保证界面、卡片、恢复流程和解释都能做到具体而无摩擦。同样的 2-of-3 数学也支撑着 Ente 账户版的 Legacy Kit它在同一套 2-of-3 数学之上叠加了由服务器介导、可撤销、且与 Ente 账户绑定的恢复流程。如果你需要面向研究项目的通用 k-of-n Shamir 共享可以参考 SLIP-0039 等标准2of3 的定位是把 2-of-3 对普通人做到零摩擦而非做一个通用密码学库。卡片上到底有什么文本码与份额格式每张卡片携带一份份额编码为一串短文本码同卡上还会渲染为 QR 码方便手机拍摄。文本码形如2of3-AQEA...2of3-前缀的作用有二让读者一眼认出这是 2of3 份额让 2of3 自身在收到非份额文本时能给出清晰的错误提示That code does not look like a 2of3 share.即该代码看起来不像 2of3 份额。源码 shamir.ts 中定义了SHARE_PREFIX 2of3-。前缀之后是 base64url 编码的字节负载负载由 10 字节头部加份额数据构成。编码与解码分别由base64UrlEncode/base64UrlDecode完成标准 base64 的、/、被替换为-、_与空参见 shamir.ts。份额格式从负载起始处读起各字节含义如下字节字段含义0版本当前格式恒为2恢复流程也兼容版本 1 创建的卡片1卡片编号1、2或3即恢复时用于合并份额的 x 值2-3长度秘密字节数的 16 位大端big-endian长度恢复时两份份额的该字段必须一致4-9随机 ID6 字节crypto.getRandomValues输出每套卡片生成时新鲜产生同一秘密的三张卡片共享该 ID10份额数据秘密字节的份额后随 4 字节校验和该校验和与秘密一起受到两卡阈值保护源码中的常量与此完全吻合shamir.ts 定义HEADER_LENGTH 104 字节版本/编号/长度 6 字节 ID、CHECKSUM_LENGTH 4、ID_LENGTH 6splitSecret在拆分前把secretBytes与checksumBytes(secretBytes)拼接为protectedBytesshamir.ts因此校验和作为被保护数据的一部分参与份额拆分——这正是下文 v2 格式安全性的来源。随机 ID 与受保护的校验和恢复为何既安全又贴心ID 与受保护的校验和是恢复流程能够安全且友好的两大支柱随机 ID 用于识别错配卡片。若把来自两套不同卡片的份额组合在一起两份卡片都能正常解析但 ID 不一致2of3 会停止并提示These two cards are from different sets. Match the ID on both cards.这两张卡片来自不同套。请匹配两张卡片上的 ID。。卡片 UI 会把 base64url 编码后的 ID 前 8 个字符作为人可读的指纹展示例如ID A1B2C3D4方便你肉眼比对卡片。校验和用于识别损坏卡片。拆分之前2of3 先把 32 位 FNV-1a 校验和附加到秘密之后合并两份卡片后重新计算校验和并与恢复出的值比对。不一致时报错 These shares did not reconstruct a valid secret.这些份额未能还原出有效秘密。而不是静默返回垃圾数据。由于校验和与秘密一起被拆分保护单张卡片无法用它来试探关于秘密的猜测。校验和的实现见 shamir.tschecksumBytes以0x811c9dc5为偏移基、0x01000193为素数通过Math.imul保证 32 位乘法语义输出 4 字节大端结果是标准 FNV-1a 32 位实现。秘密大小的上限2of3 接受的最大秘密为 2048 字节格式层面的MAX_SECRET_BYTES见 shamir.ts但实际限制是能放进一张可读的 QR 码加四行打印文本的卡片目前约为 200 字节文本。这一实际上限由打印布局决定卡片渲染逻辑把份额文本按每行最多 80 字符、共 4 行打印PRINTED_SHARE_CHARS_PER_LINE 80、PRINTED_SHARE_LINE_COUNT 4见 export.tsmaxSecretBytesForEncodedShareLength据此反推可容纳的秘密字节数。界面中Secret输入框下方的字节计数器会实时显示当前上限。格式兼容性版本 1 与版本 2早期版本创建的是版本 1 卡片校验和明文存放单张卡片即可确认低熵秘密的猜测。版本 2 把校验和保护在两卡阈值之后堵住了这一缺口。恢复流程同时支持两个版本shamir.ts 中的parseShare会读取版本号版本 1 使用LEGACY_HEADER_LENGTH 14——即 4 字节版本/编号/长度 6 字节 ID 4 字节明文校验和数据区仅含秘密字节版本 2 使用 10 字节头部校验和随秘密一起拆分。如果你持有较旧的离线恢复页面重新生成一套卡片即可获得当前版本或在 2of3.ente.com 上直接恢复。若某张版本 1 卡片可能已经暴露且秘密可被猜测请在制作新卡片前先轮换rotate该秘密。恢复算法任意两张卡片如何还原秘密恢复的关键在 shamir.ts 的combineShares。它的步骤可以概括为解析两份额并做三组前置校验版本必须一致These two cards use different formats.ID 与长度必须一致否则报来自不同套卡片编号必须不同否则报Use two different cards from the same set.即需使用同一套中两张不同的卡片。逐字节插值。设两份份额的编号分别为i₁、i₂分母denominator i₁ ^ i₂对每个字节计算output[i] gfMul(share₁[i], gfDiv(i₂, denominator)) ^ gfMul(share₂[i], gfDiv(i₁, denominator))由于份额 1 s^r、份额 2 s^ (r·2)GF(256) 乘法把上式按 XOR 对加法和乘法分配律展开后随机项_r_恰好抵消_s_项系数为 1于是逐字节精确还原出秘密 校验和。gfDiv通过gfInv利用gfPow(value, 254)求逆元实现域内除法。校验对前length字节重新计算 FNV-1a 校验和并与恢复出的尾部 4 字节比对不一致即报These shares did not reconstruct a valid secret.。最后用严格 UTF-8 解码TextDecoder(utf-8, { fatal: true })非 UTF-8 文本报These shares did not reconstruct readable text.。从源码结构可以看出恢复流程在parseShare阶段就已把 ID、编号、长度全部提取出来用于人类可读的状态提示如Card 2 from ID A1B2C3D4并在界面层index.tsx预先检测ID 或长度不匹配与重复卡片两类常见错误尽早阻断无效组合。离线恢复文件2of3-recovery.html点击Download all cards时2of3 会在三张卡片 PNG 之外附带一个额外文件2of3-recovery.html——恢复流程的一份离线副本。其中包含一个独立的 HTML 页面承载与 2of3.ente.com 相同的恢复界面完整的 Shamir 合并逻辑以内联 JavaScript 形式写出一个打包进文件内的 QR 解码器可上传打印卡片的手机照片并本地解码零网络请求不依赖 Ente、不依赖 2of3.ente.com、也不依赖任何 CDN。双击即可在任意现代浏览器中打开。用该文件恢复与在 2of3.ente.com 上恢复完全一致放入两张卡片图片或文本点击Recover secret复制结果。这个文件回答了如果 2of3.ente.com 十年后不在了怎么办答案是什么都不会发生——打开与卡片一同保存的 HTML 文件即可恢复。即使 Ente 公司整体消失这个文件也不需要从我们这里获取任何东西。它只是几百行可审查的 JavaScript做的正是 2of3.ente.com 所做的事。[!IMPORTANT]请像对待备份一样对待离线恢复文件。至少在一个耐久的介质上保存一份副本U 盘、备份或若存放在保险柜中与卡片一起打印。卡片是秘密本身恢复文件是打开它们的工具。从源码看该文件由createOfflineRecoveryHtml生成export.ts完整的 Shamir 实现、份额解析、图像读取与 QR 解码逻辑都以字符串模板内联进 HTML卡槽Card A / Card B支持上传图片、粘贴代码与拖放文件恢复按钮直接调用内联的combineShares。QR 解码器源码由 offline-qr-source.ts 在构建期从qr-raw模块的源码文本剥离export关键字、去除 source map 注释后拼接而成最终暴露为globalThis.__twoOf3DecodeQR供页面调用——因此离线文件中的解码能力与在线版完全同源。这个文件还是一个可验证目标因为它是自包含的纯 JavaScript你可以通读它、做静态分析或与 web/apps/twoof3 下的开源源码逐一对照说服自己它确实言行一致——没有任何远程服务需要信任。QR 解码器对真实手机照片的宽容处理2of3 的恢复流程接受的不只是裁剪干净的 QR 图片。打印卡片的手机照片通常让 QR 只占画面的一部分四周是卡片其余内容。为了不强迫用户手动裁剪QR 解码器会对每张上传图片尝试多轮解码实现见 qr-code.ts以完整尺寸解码整张图片对整张图片做自动方形裁剪cropToSquare后解码依次做三次逐步收紧的卡片形裁剪——每轮都裁到 2of3 卡片上 QR 的大致位置三组比例参数为[0.086, 0.213, 0.829, 0.591]、[0.135, 0.238, 0.73, 0.49]、[0.16, 0.255, 0.68, 0.52]每次裁剪后分别以不裁剪和自动方形裁剪两种方式解码。第一个返回合法代码的尝试即获胜。实际上一张整卡在画面内、对焦正常的随手手机照片会在第一次或第二次卡片形裁剪时成功解码如果你已经把图片裁剪得紧贴 QR则整图尝试就会成功。同一套逻辑被打包进 离线恢复文件因此离线恢复同样宽容。若所有尝试都失败恢复流程会提示 Could not read that QR code.无法读取该 QR 码。你可以换一张更清晰的照片或直接粘贴文本码。图片读取与画布绘制走createImageBitmap优先、HTMLImageElement兜底的路径解码失败的图片会报 Could not read that image.参见 qr-code.ts。值得一提的是卡片的 QR 码生成使用medium纠错级别qr-code.ts在信息密度与抗污损之间取了平衡这也是手机照片能顺利解码的工程基础之一。从原理到实践卡片完整生命周期回顾把原理落到使用上一套 2of3 卡片的完整生命周期如下拆分在浏览器内输入秘密Ente 恢复密钥、密码管理器主密码、钱包助记词、全盘加密密钥或 2FA 备份码等splitSecret在本地用crypto.getRandomValues完成拆分——秘密在渲染任何内容之前就已本地拆分为份额卡片图片与 QR 均在本地构建没有任何内容会被发送给 Ente。制卡与分发每张卡片可单独打印、下载为高分辨率 PNG、通过系统分享面板分享、或仅复制2of3-开头的文本码Download all cards则一次性下载三张 PNG 与离线恢复文件。下载的 PNG 遵循${标签}-set-${指纹}-card-${编号}.png的命名规则export.ts。务必把三张卡片存放在三个相互独立的地方切勿同处存放——两张卡片合起来就是完整秘密。验证存放前用任意两卡最好是 12、13、23 全部组合测试恢复以捕获打印模糊、下载截断、混入异套卡片或复制丢字符等问题。恢复随时回到 2of3.ente.com 或打开离线文件放入任意两张同套卡片图片上传、拖放或粘贴文本码皆可点击Recover secret得到原秘密。若不慎同时丢失两张卡片则仅剩一份份额无法重构此时应把秘密视为已丢失并按需轮换。完整的界面操作步骤见 Getting started常见问题见 FAQ2of3 的整体定位见 2of3 概述。总结2of3 的可靠性建立在三层设计之上数学层用 GF(256) 上的 Shamir 2-of-3 秘密共享保证任意两卡还原、单卡无信息格式层用 10 字节头部 份额数据的编码承载版本、卡片编号、长度、随机 ID 与受保护的 FNV-1a 校验和让错配、损坏与格式不兼容都可被明确检测分发层用可打印卡片、可复制的文本码与完全离线的2of3-recovery.html保证恢复不依赖任何在线服务。整条链路拆分、制卡、恢复、离线文件生成都落在 web/apps/twoof3 的开源源码中任何持有卡片的人都可以依据本文所述的份额格式与源码审计亲手验证甚至独立实现一套自己的恢复工具。【免费下载链接】ente End-to-end encrypted cloud for everything.项目地址: https://gitcode.com/GitHub_Trending/en/ente创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考