ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入解析 Cap 工作原理:基于 SHA-256 工作量证明与 instrumentation 的自托管 CAPTCHA

深入解析 Cap 工作原理:基于 SHA-256 工作量证明与 instrumentation 的自托管 CAPTCHA 网络安全应用安全后端【免费下载链接】capFree, open-source and self-hosted CAPTCHA alternative to reCAPTCHA. Privacy-first and powered by proof-of-work and instrumentation challenges.项目地址https://gitcode.com/gh_mirrors/cap13/cap点击查看免费下载Cap 是一个免费、开源、可自托管的 CAPTCHA 替代方案与 reCAPTCHA 相比更注重隐私保护。本指南基于 docs/guide/workings.md泰文版深入剖析其底层机制浏览器端如何注册自定义元素、向服务端请求 challenge、用 Rust 编写的 WASM 配合 Web Worker 并行求解 SHA-256 工作量证明Proof-of-Work、在沙箱 iframe 中执行 instrumentation 挑战最后将解答换回服务端签名 token 的完整流程。读完本文你将掌握 Cap 从初始化到验证的全链路设计并理解每一条配置项背后对应的源码实现。一、整体工作流概览Cap 的核心流程可以归纳为三个阶段初始化与请求 challengeCap 在浏览器中自动注册自定义元素cap-widget创建 shadow DOM当用户请求解答时向自托管服务端请求 challenge服务端返回 token、挑战配置以及可选的压缩后的 instrumentation 数据。计算解答客户端用 Rust 编写的 WASM 模块配合 Web Worker 并行执行 SHA-256 哈希求解寻找满足目标前缀条件的 nonce如果包含 instrumentation 挑战则在沙箱 iframe 中解压并执行。兑换 token客户端把解答提交回服务端服务端用相同 token 与配置重新生成挑战并验证解答验证通过后签发可用于请求认证的 token。该页面属于技术性说明覆盖 SHA-256 与 instrumentation 两类挑战不涉及 HashWX基于 HashWX 的 challenge 是另一套协议。若想要整体效果层面的概述可阅读 有效性说明。二、初始化自定义元素与 shadow DOM当 Cap 被加载时它在浏览器中自动注册自定义元素。源码位于 widget/src/src/cap.js其中定义了一个继承自HTMLElement的CapWidget类class CapWidget extends HTMLElement { static formAssociated true; // ... }在connectedCallback中完成 shadow DOM 的创建与 UI 构建async connectedCallback() { this.#host this; if (!this.shadowRoot) { this.#shadow this.attachShadow({ mode: open }); } else { this.#shadow this.shadowRoot; } if (!this.#div) this.#div document.createElement(div); this.#resolveI18n(); this.createUI(); this.addEventListeners(); this.initialize(); // ... }关键点包括form-associated 自定义元素static formAssociated true使该组件能参与原生表单校验attachInternals()用于设置有效性状态。shadow DOM 隔离UI 样式与 DOM 结构封装在 shadow root 中避免污染宿主页面也防止外部样式干扰组件内部渲染。隐藏输入域验证成功后token 被写入一个隐藏的input typehidden namecap-token名称可通过data-cap-hidden-field-name属性自定义随表单一起提交给服务端。Worker 池预初始化initialize()中会创建共享的 Worker 脚本 Blob URL_getSharedWorkerUrl以及一个初始WorkerPool为后续并行求解做好准备。三、请求 challengetoken、配置与 instrumentation3.1 服务端生成挑战当用户触发验证点击或调用solve()时客户端向 API 端点发送POST请求。请求的端点由属性data-cap-api-endpoint指定或通过window.CAP_CUSTOM_FETCH自定义请求函数默认路径为/{siteKey}/challenge。服务端路由实现在 standalone/src/cap.js.post(/:siteKey/challenge, async ({ set, params, request, server: srv }) { ... })服务端会先进行一系列前置检查Site Key 校验从数据库读取key:{siteKey}的config与jwtSecret找不到则返回 404。IP 封锁规则通过isBlocked检查精确 IP、CIDR、ASN、国家等封锁规则见 standalone/src/cap.js 中loadBlockRules/isBlocked。UA 与请求头过滤若开启blockNonBrowserUA则要求 UA 匹配浏览器特征若配置了requiredHeaders则逐头校验。限流默认每 5 秒 30 次请求可通过 site key 的ratelimitMax/ratelimitDuration覆盖。随后服务端根据 site key 配置选择协议并调用capjs-core的generateChallenge(jwtSecret, challengeOpts)见 core/src/index.js。以 SHA-256 PoW 为例challengeOpts { challengeCount: keyConfig.challengeCount ?? 80, challengeSize: keyConfig.saltSize ?? 32, challengeDifficulty: keyConfig.difficulty ?? 4, expiresMs: CHALLENGE_TTL_MS, // 15 分钟 scope: params.siteKey, instrumentation: instrumentationOpts, };3.2 挑战的生成与 token 签名在 core/src/index.js 的generateChallenge中SHA-256 挑战的生成逻辑如下const payload { n: randomHex(25), // 随机 nonce 种子 c, // challenge 数量 s, // salt 长度 d, // 目标前缀难度前导 0 的个数 exp: expires, // 过期时间 iat: now, // 签发时间 }; if (opts.scope) payload.sk String(opts.scope); // ... const token jwtSign(payload, secret); const result { challenge: { c, s, d }, token, expires };几个重要参数与默认值来自 core/src/index.js参数默认值范围限制说明challengeCount(c)50core/ 80standalone1 ~ 1000生成多少个独立 PoW 挑战challengeSize(s)321 ~ 256每个 salt 的十六进制长度challengeDifficulty(d)41 ~ 16目标前缀中需要匹配的位数前导 0 数量expiresMs10 分钟core/ 15 分钟standalone—challenge 有效期生成的 token 是一个 JWT由jwtSign(payload, secret)签发包含随机种子、挑战参数、过期时间、scopesite key以及可选的 instrumentation 元数据。服务端持有 secret客户端只能看到签名后的 token无法伪造或篡改其中的参数。若开启了 instrumentation服务端会调用generateInstrumentationcore/src/instrumentation.js生成一段压缩的 JS 脚本并把其元数据id、expectedVals、vars、blockAutomatedBrowsers、expires用 GCM 加密后写入 token 的ei字段。3.3 响应结构POST /challenge的响应形如{ challenge: { c: 80, s: 32, d: 4 }, token: JWT, expires: 1700000000000, instrumentation: base64 压缩后的 JS 脚本可选 }客户端拿到响应后基于 token 生成多个挑战。客户端侧的挑战生成在 widget/src/src/cap.js 中完成核心是一个确定性 PRNG伪随机数生成器challenges Array.from({ length: challenge.c }, () { i; return [ prng(${token}${i}, challenge.s), // salt prng(${token}${i}d, challenge.d), // target 前缀 ]; });prng使用 FNV-1a 哈希 Xorshift 实现见 core/src/prng.jsexport function prngFromHash(initialHash, length) { let state initialHash; let result ; while (result.length length) { state ^ state 13; state ^ state 17; state ^ state 5; state 0; result state.toString(16).padStart(8, 0); } return result.substring(0, length); }关键设计客户端并不从服务端逐个接收 salt 与 target而是用「token 序号」作为种子自行推导。服务端验证时同样用fnv1aResume按相同算法重放生成因此双方无需传输大量挑战数据也保证了挑战的不可预测性——攻击者无法预选一个容易解的 challenge。四、计算解答WASM Web Worker 并行求解4.1 求解目标每个挑战要求找到 noncen使得SHA-256(salt nonce)得到的哈希值以目标前缀即d个前导零的十六进制串开头。目标前缀的长度由难度d决定d每增加 1期望计算量翻倍每个哈希命中概率为 1/16^d。4.2 Rust 编写的 WASM 求解器WASM 模块的 Rust 源码在 wasm/src/rust/src/lib.rs。其核心是solve_pow(salt, target)函数实现了一个4 路 SIMD 并行 SHA-256sha256_4way_single_block使用v128向量指令同时处理 4 个 nonce并针对「消息长度 56 字节」的单块哈希场景做了专门优化用u32x4向量同时打包 4 个 nonce 的消息块每轮迭代批量推进 4 个候选 nonce当 nonce 的十进制位数变化导致消息长度不齐时回退到标量检查check_full_match_scalar。WASM 二进制由wasm-bindgen导出浏览器端通过WebAssembly.compile加载见 widget/src/src/cap.js 的getWasmModule默认从 CDN 拉取也可用window.CAP_CUSTOM_WASM_URL覆盖。4.3 Worker 并行与容错客户端使用WorkerPoolwidget/src/src/cap.js管理多个 Web Worker默认 worker 数量取navigator.hardwareConcurrency上限 16可通过data-cap-worker-count属性限制每个 worker 收到{ salt, target, wasmModule }后优先用 WASM 求解widget/src/src/worker.js若 WASM 不可用则回退到纯 JS 的crypto.subtle.digest逐批求解速度约慢 10 倍且会显示提示警告worker 意外崩溃时_replaceWorker会自动替换最多重试 3 次并重新调度队列中的任务每个任务有 60 秒超时保护TASK_TIMEOUT_MS。worker 内部widget/src/src/worker.js的 SHA-256 求解流程const solveFallback async ({ salt, target }) { let nonce 0; // ...将 target 前缀转为字节与掩码 while (true) { for (let i 0; i batchSize; i) { const inputString salt nonce; const hashBuffer await crypto.subtle.digest(SHA-256, encoder.encode(inputString)); // 检查哈希是否匹配目标前缀 if (matches) { self.postMessage({ nonce, found: true }); return; } nonce; } } };4.4 推测式预求解Speculative Solving值得一提的优化是推测式预求解在用户首次交互mousemove/touchstart/keydown且组件可见时Cap 会提前 2.5 秒SPECULATIVE_DELAY_MS悄悄发起challenge请求并开始求解先只用 1 个 worker降低干扰。当用户真正点击验证按钮时若预求解已拿到 token直接使用缓存 token体验近乎瞬时若正在求解则通过promoteFn把并发 worker 数提升到完整配置#workersCount加速完成。这解释了为什么 Cap 对真实用户几乎「隐形」——验证往往在用户点按之前就已经在后台完成了。五、instrumentation 挑战沙箱 iframe 中的环境检测5.1 生成与压缩当 site key 配置开启instrumentation时服务端会在生成挑战的同时生成一段 instrumentation 脚本。源码见 core/src/instrumentation.js生成 4 个随机变量vars每个初始化为随机值生成 20 条随机混合的位运算、函数调用与 DOM 操作等式clientEqs例如clientEqs ${vD} ~(${vD} ${vS1});; clientEqs ${vD} ${vD} ^ ${vS1};; clientEqs ${vD} ${fnHelper}(${vS1}, ${vS2}, ${vD});; clientEqs ${vD} ${domHelper}(${vS1}, ${vS2}, ${vS3});;脚本内嵌多种自动化检测检查navigator.webdriver、Selenium/WebDriver/Puppeteer 等标记属性、UA 特征、WebGL 渲染器、Function.prototype.toString是否被篡改、栈帧特征、字体宽度指纹等根据obfuscationLevel1~10对脚本做不同程度的混淆≤3 仅压缩空白≤7 用 esbuild 压缩 字符串数组化≥8 用javascript-obfuscator做控制流平坦化、死代码注入、RC4 字符串加密等最后用deflateRawSync压缩并以 base64 返回。5.2 客户端解压与执行客户端收到instrumentation字段后在 widget/src/src/cap.js 的runInstrumentationChallenge中执行解压优先使用浏览器原生DecompressionStream(deflate-raw)不可用时回退加载 pako 库沙箱隔离创建一个1x1、不可见、sandboxallow-scripts的 iframe将解压后的脚本写入srcdoc后注入页面超时控制20 秒内未收到结果则标记__timeout: true通信脚本在 iframe 内通过postMessage({ type: cap:instr, ... })回报结果主页面用message事件监听只接受来自该 iframecontentWindow的消息。iframe 的 sandbox 只授予allow-scripts脚本无法访问父页面 DOM 或其它页面资源只能在受限环境中读取自身的navigator、document等 API——这正是「在沙箱 iframe 中解压并求解」的实现方式。5.3 结果验证服务端在redeem阶段用verifyInstrumentationResultcore/src/instrumentation.js验证结果中的iid必须与 token 中加密的 instrumentation id 一致4 个变量的最终值必须与expectedVals完全一致——正常浏览器执行等式链会得到服务端预先计算的值而自动化工具或非浏览器环境通常会在某个环境检查处提前return null导致结果缺失或不匹配若启用了blockAutomatedBrowsers还会对探针数据执行detectAutomation命中则拒绝。这保证了 instrumentation 结果是在真实浏览器环境中计算出来的且每次挑战的等式链、变量名、混淆方式都随机生成难以被固定脚本绕过。六、兑换解答服务端重放验证与 token 签发6.1 提交解答客户端求解完成后向POST /{siteKey}/redeem提交{ token: challenge JWT, solutions: [12345, 67890, ...], instr: { i: ..., state: { ... }, p: { ... }, ts: 1700000000000 } }其中solutions是与挑战一一对应的 nonce 数组顺序敏感instr是 instrumentation 的执行结果若存在。6.2 服务端重放验证在 standalone/src/cap.js 的 redeem 路由中服务端调用capjs-core的validateChallenge(jwtSecret, body, opts)core/src/index.js核心步骤验证 tokenjwtVerify检查 JWT 签名与完整性重放生成挑战服务端用 token 内携带的参数重新生成相同的挑战。对 SHA-256 PoW用与客户端完全相同的fnv1aResume递推计算出每个 salt 与 targetconst tokenFnv fnv1a(token); for (let i 0; i c; i) { const saltSeed fnv1aResume(tokenFnv, String(i 1)); const targetSeed fnv1aResume(saltSeed, d); const salt prngFromHash(saltSeed, size); const target prngFromHash(targetSeed, difficulty); const hash sha256Bytes(salt solutions[i]); if (!powMatchesPrefix(hash, parseHexPrefix(target))) { return fail(invalid_solution); } }验证 instrumentation若存在解密 token 中的ei字段拿到元数据若instr_blocked且配置了blockAutomatedBrowsers→ 拒绝若instr_timeout→ 拒绝429否则用verifyInstrumentationResult核对结果防重放nonce 消费通过consumeNonce回调用 token 的签名哈希jwtSigHex作为 key 在数据库执行SET ... NX EX原子占用。若该 challenge 已被兑换过返回already_redeemed。这从根本上杜绝了截获验证请求后的重放攻击。6.3 签发 token验证通过后signToken回调生成一个一次性随机令牌randomBytessignToken: () { const redeemId randomBytes(8).toString(hex); const redeemSecret randomBytes(15).toString(hex); return ${params.siteKey}:${redeemId}:${redeemSecret}; },并以 TTLstandalone 为 2 小时存入数据库token:{token}返回给客户端{ success: true, token: redeem token, expires: 1700007200000 }客户端把该 token 写入隐藏表单域或通过solve事件回调获取随业务请求提交给应用服务端。应用服务端只需校验这个 token 存在且未过期即可放行请求——它不需要再次运行任何 PoW 验证。七、安全模型小结Cap 的整套设计围绕三个安全目标展开目标实现机制源码位置防伪造challenge 参数与 instrumentation 元数据都经 JWT 签名 GCM 加密客户端无法篡改core/src/index.js防重放redeem 时对 token 签名哈希做 NX/EX 原子占用一次性使用standalone/src/cap.js 的consumeNonce防自动化滥用PoW 消耗真实算力 instrumentation 检测浏览器环境使批量滥用成本高昂wasm/src/rust/src/lib.rs、core/src/instrumentation.js同时由于所有组件均可自托管、无中央服务器、默认无 Cookie 与遥测Cap 在隐私方面也具备天然优势——验证所需的唯一密钥是 site key 的jwtSecret它只存在于你自己的服务端。八、进一步阅读HashWX 挑战机制另一种基于内存硬性哈希的 PoW 协议用于对抗 GPU 加速破解有效性说明为什么 PoW instrumentation 的组合能有效提高滥用成本集成与用法指南如何在你的站点中安装与配置 Cap 组件独立部署文档Docker 部署、环境变量与路由说明核心实现与测试挑战生成与验证见 core/src/index.js对应测试在 core/test/core.test.js服务端路由见 standalone/src/cap.js测试在 standalone/test/cap-routes.test.js浏览器端组件见 widget/src/src/cap.js 与 widget/src/src/worker.jsWASM 求解器见 wasm/src/rust/src/lib.rs。赞分享网络安全应用安全后端【免费下载链接】capFree, open-source and self-hosted CAPTCHA alternative to reCAPTCHA. Privacy-first and powered by proof-of-work and instrumentation challenges.项目地址https://gitcode.com/gh_mirrors/cap13/cap点击查看免费下载相关推荐Cap 内部工作原理自托管 CAPTCHA 的 SHA-256 工作量证明与埋点挑战全流程解析Cap 内部工作原理自托管 CAPTCHA 的 SHA 256 工作量证明与埋点挑战全流程解析 本文深入剖析 Cap 这一自托管、开源、隐私优先的 CAPTC网络安全应用安全后端sqlpp11事务处理最佳实践确保数据一致性的完整指南sqlpp11事务处理最佳实践确保数据一致性的完整指南 sqlpp11是一个C类型安全SQL模板库提供了强大的事务处理机制帮助开发者确保数据库操作的数网络安全应用安全后端Cap 对比 SilentShield自托管工作量证明 CAPTCHA 与托管式行为分析机器人防护的全面对比Cap 对比 SilentShield自托管工作量证明 CAPTCHA 与托管式行为分析机器人防护的全面对比 导读 本文以 Cap 项目与德国 Forge1网络安全应用安全后端上一篇DataHen Till HTTP缓存机制深度解析如何实现爬虫断点续传下一篇微软技术面试通关指南2025年高频算法题分类实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表