thakares/chronoseal-rs

GitHub: thakares/chronoseal-rs

ChronoSeal 是一个基于 Rust 和 WASM 的无感式加密反自动化框架,通过 Ed25519 签名心跳链、随机栈机和行为熵验证持续证明浏览器真实存在,使 AI 爬虫和自动化工具在大规模抓取时面临极高成本。

Stars: 4 | Forks: 0

# ChronoSeal

ChronoSeal Logo

**使用 Rust、WASM 和行为连续性验证构建的加密反自动化和浏览器证明框架。** ChronoSeal 使 AI 爬虫、无头浏览器和自动化工具在模拟真实用户时具有极高的计算成本和操作复杂性,同时对合法的人类访问者保持完全隐形且无摩擦体验。 ## 工作原理 ChronoSeal 为每个浏览器会话建立连续的、密码学上可验证的存在证明。其灵感来源于 IoT 固件(ESP32 级别设备)中使用的心跳模型:客户端必须持续发出经过签名的、链式连接的证明,否则会话将被静默失效。 ``` Browser Server │ │ │ WASM loads, generates Ed25519 keypair │ │ Private key never leaves WASM memory │ │ │ ├──── POST /init { public_key } ──────────►│ Store session, salt, initial hash │◄─── { session_id, salt, opcodes, H0 } ────┤ │ │ │ Every 12–25s (jittered): │ │ ┌─ Collect mouse entropy │ │ ├─ Execute VM opcodes → stack state │ │ ├─ Compute H(n) = Blake3(H(n-1) ║ …) │ │ └─ Sign payload with Ed25519 │ │ │ ├──── POST /hb { session_id, sig, … } ────►│ Verify sig → chain → behavior → fingerprint │◄─── { status, next_salt } ────────────────┤ Rotate salt, advance chain │ │ │ On failure: server returns {"status":"ok"}│ Silent rejection — indistinguishable ``` ## 安全模型 ### ChronoSeal 防御的威胁 | 威胁 | 机制 | |---|---| | Playwright / Puppeteer Stealth | 鼠标熵验证拒绝合成或缺失的移动 | | 重放攻击 | 哈希链——每个心跳引用前一个哈希;旧的有效载荷无效 | | 签名伪造 | Ed25519 私钥在 WASM 内部生成,从不被序列化或暴露给 JS | | 时钟篡改 | 服务器强制执行 ±30 秒的时间戳窗口 | | 凭证共享 | 会话与每次页面加载时新生成的密钥对绑定 | | 洪泛虚假会话 | 每会话速率限制(10 秒内 5 次请求);每 60 秒驱逐过期条目 | | 流量被动分析 | 服务器始终返回 `{"status":"ok"}` —— 拒绝是静默的 | ### ChronoSeal 不宣称防御的内容 ChronoSeal 是一种提高成本的机制,而非坚不可摧的屏障。拥有足够动力的攻击者,只要有真实的浏览器、真实的输入设备以及逆向 WASM 的耐心,就可以绕过它。我们的目标是使抓取成本和操作复杂性提高到在大规模下变得不切实际。 ## 架构 ``` chronoseal-rs/ ├── shared/ Shared types, Blake3 hash-chain logic, constants ├── server/ Axum HTTP server │ ├── routes/ /init and /hb handlers │ ├── session.rs Session lifecycle: create, verify, advance chain │ ├── crypto.rs Ed25519 signature verification (BTreeMap canonical JSON) │ ├── trust.rs Behavioral signal validation (mouse speed, pauses) │ ├── fingerprint Browser fingerprint sanity checks │ ├── vm.rs Random opcode program generator │ ├── ratelimit.rs Token-bucket rate limiter with periodic eviction │ └── cleanup.rs Background task: expire sessions + evict rate limiter ├── wasm/ Rust → WASM client module │ ├── crypto.rs Ed25519 keypair, signing, hash computation │ └── vm.rs Stack machine executor (PUSH/ADD/SUB/MUL/XOR/AND/OR/ROT/NOT/HASH) └── frontend/ Vanilla JS glue ├── heartbeat.js Session init, heartbeat scheduling, chain advancement └── entropy.js Mouse event collection ``` ### 栈机 服务器在会话初始化时生成一个随机程序(8-16 个操作码)。客户端在每次心跳时执行该程序,并将生成的栈状态包含在签名的有效载荷中。这使得每个心跳在结构上都是唯一的,无需任何服务器往返通信。 | 操作码 | 助记符 | 效果 | |--------|----------|--------| | `0x00` | PUSH u32 | 压入 4 字节小端字面量 | | `0x01` | ADD | 弹出 2 个,压入 `a + b` (溢出回绕) | | `0x02` | SUB | 弹出 2 个,压入 `a - b` (溢出回绕) | | `0x03` | MUL | 弹出 2 个,压入 `a * b` (溢出回绕) | | `0x04` | XOR | 弹出 2 个,压入 `a ^ b` | | `0x05` | AND | 弹出 2 个,压入 `a & b` | | `0x06` | OR | 弹出 2 个,压入 `a \| b` | | `0x07` | ROT | 弹出 2 个,压入 `a.rotate_left(b % 32)` | | `0x08` | NOT | 弹出 1 个,压入 `!a` (一元运算) | | `0x09` | HASH | 整个栈的 Blake3 哈希 → 单个 u32 | ### 哈希链 ``` H(0) = Blake3( session_id ║ pub_key ║ salt₀ ) H(n) = Blake3( saltₙ₋₁ ║ H(n-1) ║ timestamp ║ Blake3(entropy_json) ║ Blake3(stack_json) ) ``` 每个心跳必须呈现与服务器存储相匹配的 `H(n-1)`。要伪造有效的 `H(n)`,需要知道私钥(用于签名)、盐(仅服务器端持有)以及所有先前的状态。 ### 签名规范形式 客户端对按键名字母顺序排序的 JSON 对象进行签名(匹配 `JSON.stringify(obj, Object.keys(obj).sort())`): ``` { "entropyData": { "events": [ { "x": …, "y": …, "t": … } ] }, "fingerprint": { "aspectRatio": "…", "devicePixelRatio": "…", "hardwareConcurrency": … }, "prevHash": "hex…", "sessionId": "hex…", "stackState": { "stack": […], "ip": … }, "timestamp": 1234567890123 } ``` 服务器在调用 `VerifyingKey::verify_strict` 之前,使用 `BTreeMap`(按字母顺序键排序)重建此结构。 ## SQLite Schema ``` CREATE TABLE IF NOT EXISTS sessions ( session_id TEXT PRIMARY KEY, public_key BLOB NOT NULL, salt BLOB NOT NULL, last_hash BLOB NOT NULL, chain_length INTEGER NOT NULL DEFAULT 1, created_at INTEGER NOT NULL, last_seen INTEGER NOT NULL, expires_at INTEGER NOT NULL ); ``` 会话存储在内存中的 SQLite 数据库中。根据设计,所有会话状态在服务器重启时都会丢失——客户端会透明地重新初始化。 ## 构建 ### 前置条件 - Rust stable (≥ 1.87) - [`wasm-pack`](https://rustwasm.github.io/wasm-pack/installer/) ### WASM ``` wasm-pack build wasm --target web --release mv wasm/pkg frontend/pkg ``` ### 服务器 ``` cargo build -p server --release ``` ### 开发(多合一) ``` bash scripts/dev.sh ``` 服务器在 `/` 路径下静态提供 `frontend/` 目录,并在 `/init` 和 `/hb` 提供 API。 ## 部署 ### 原生 + systemd ``` cargo build -p server --release sudo cp target/release/server /usr/local/bin/chronoseal sudo cp chronoseal.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now chronoseal ``` ### Docker ``` docker compose up -d --build ``` ### 反向代理 将 ChronoSeal 置于 nginx、Nginx Proxy Manager 或 HAProxy 之后。启用: - TLS 1.3 - HTTP/2 - 积极的上游超时设置(心跳间隔为 12-25 秒) ## 集成 在任何受保护的页面中加入这两行: ``` ``` `main.js` 调用 `initHeartbeat()`,该函数负责处理 WASM 加载、会话初始化,并自动调度所有后续心跳。没有可见的 UI,没有 CAPTCHA,也不需要用户交互。 ## 配置 所有可调常量位于 `shared/src/constants.rs` 中: | 常量 | 默认值 | 描述 | |---|---|---| | `SESSION_ID_LEN` | 32 字节 | 会话 ID 熵 | | `SALT_LEN` | 16 字节 | 每次心跳的盐值大小 | | `HEARTBEAT_MIN_INTERVAL_MS` | 12 000 毫秒 | 最小心跳间隔 | | `HEARTBEAT_MAX_INTERVAL_MS` | 25 000 毫秒 | 最大心跳间隔(均匀抖动) | | `EXPIRATION_MINUTES` | 30 分钟 | 上次心跳后的会话生命周期 | | `RATE_LIMIT_COUNT` | 5 | 每个窗口的最大心跳数 | | `RATE_LIMIT_WINDOW_SECS` | 10 秒 | 速率限制窗口 | | `MAX_TIMESTAMP_DRIFT_MS` | 30 000 毫秒 | 防重放时间戳窗口 | | `MIN_MOUSE_TOTAL_DIST` | 10.0 px | 最小累积鼠标移动距离 | | `MAX_MOUSE_AVG_SPEED` | 2.0 px/ms | 最大平均鼠标速度 | | `MIN_PAUSE_COUNT` | 1 | 最小鼠标暂停事件数 | ## 许可证 [MIT OR Apache-2.0](LICENSE)
标签:Blake3, CISA项目, Ed25519, Rust, WASM, WebAssembly, Web安全, 人机验证替代方案, 前端安全, 反AI抓取, 反机器人, 反爬虫, 可视化界面, 堆栈机, 客户端验证, 密码学, 心跳链, 手动系统调用, 抗指纹, 数据可视化, 无头浏览器检测, 无状态, 机器人检测, 浏览器指纹, 浏览器证明, 网络流量审计, 自动化防御, 蓝队分析, 行为熵验证, 请求拦截, 通知系统, 防伪造, 防自动化, 隐形验证, 零摩擦