libcaptcha/motion-attestation
GitHub: libcaptcha/motion-attestation
基于行为生物特征的人机验证库,通过分析鼠标移动、击键动态、触摸压力等多维度交互信号,识别并阻断自动化工具和机器人。
Stars: 1 | Forks: 0
# motion-attestation
通过行为生物识别进行人机交互验证。分析鼠标移动、点击模式、击键动态、滚动行为、触摸压力和设备传感器,以区分人类与机器人。返回完整性评分和异常标记。零依赖。
使用 undetected-chromedriver 的高级机器人可以通过信号检测。它们无法复制人手的不自主生物力学模式。
## 安装
```
npm install motion-attestation
```
## 用法
### 收集信号 (浏览器)
```
import { createCollector } from 'motion-attestation';
const collector = createCollector();
collector.attach();
// Track clicks on specific elements
collector.bind(document.getElementById('submit'), 'submit-btn');
collector.bind(document.getElementById('agree'), 'agree-checkbox');
// Wait for enough data (3-15 seconds of interaction)
const interval = setInterval(() => {
if (collector.isReady()) {
clearInterval(interval);
const data = collector.getData();
collector.detach();
// Send data to server for analysis
}
}, 500);
```
### 分析交互 (服务器)
```
import { analyze, classifyScore } from 'motion-attestation';
const { score, penalty, reasons, categories } = analyze(data);
// score: 0.0-1.0 (1.0 = human, 0.0 = bot)
// penalty: total deductions
// reasons: ["[mouse] Low curvature entropy: 0.82 (straight-line)"]
// categories: per-category { penalty, maxPenalty, reasons }
const verdict = classifyScore(score);
// "human" | "suspicious" | "bot"
```
### 挑战-响应协议
```
// Server
import { createServer } from 'motion-attestation';
const attestation = createServer({
secretKey: process.env.MOTION_SECRET, // optional
scoreThreshold: 0.5,
});
// Mount on existing HTTP server
import { createServer as createHttpServer } from 'node:http';
const httpServer = createHttpServer(attestation.handler());
httpServer.listen(3000);
// Verify tokens from downstream services
const payload = attestation.validateToken(token);
```
```
// Client (browser)
import { createCollector } from 'motion-attestation';
// 1. Init challenge
const { challengeId } = await fetch('/interactions/init', {
method: 'POST',
}).then((r) => r.json());
// 2. Collect interactions
const collector = createCollector();
collector.attach();
// 3. Submit when ready
const data = collector.getData();
collector.detach();
const response = await fetch('/interactions/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cid: challengeId, d: data, ts: Date.now() }),
});
const { cleared, score, token, flags } = await response.json();
```
### Token 签名
```
import { signToken, verifyToken, generateKey } from 'motion-attestation';
const key = generateKey();
const token = signToken({ score: 0.95, iat: Date.now() }, key);
const payload = verifyToken(token, key); // null if invalid/expired
```
## 协议
```
Client Server
| |
|--- POST /interactions/init ------------->| Create challenge
|<-- { challengeId, ttl } -----------------|
| |
| +----------------------+ |
| | Collect for 3-15s: | |
| | * Mouse movement | |
| | * Click positions | |
| | * Keystroke timing | |
| | * Scroll patterns | |
| | * Touch + pressure | |
| | * Gyro/Accel sensors | |
| | * Event ordering | |
| +----------------------+ |
| |
|--- POST /interactions/verify ----------->| Analyze biometrics
| { cid, d, ts } |
|<-- { cleared, score, token, flags } -----|
| |
|--- GET /protected ---------------------->| Bearer token validation
| Authorization: Bearer |
|<-- { message, score } -------------------|
```
## 收集的信号
| 类别 | 数据 | 桌面 | 移动 |
| -------------- | -------------------------------------- | :--: | :--: |
| 鼠标位置 | 带时间戳的亚像素 x,y | \* | |
| 点击落点 | 距目标中心的偏移量 + 停留时间 | \* | |
| 击键时序 | 按键持续时间 + 键间间隔 | \* | \* |
| 滚动行为 | 位置、增量、时间戳 | \* | \* |
| 触摸事件 | 位置、压力、接触半径 | | \* |
| 加速度计 | 三轴加速度读数 | | \* |
| 陀螺仪 | 三轴旋转速率 | | \* |
| 设备方向 | Alpha, beta, gamma 角度 | | \* |
| 事件顺序 | 所有事件类型的时间戳序列 | \* | \* |
| 绑定元素命中 | 每个绑定元素距中心的点击偏移 | \* | \* |
| 参与度 | 首次交互时间、会话持续时间 | \* | \* |
## 分析算法
### 鼠标移动 — _权重:0.30_
核心信号。人类运动控制遵循生物力学限制,这些限制极难伪造。
| 检查项 | 人类 | 机器人 | 惩罚值 |
| -------------------- | -------------------------------- | ---------------------------------- | --------- |
| **曲率熵** | 曲率可变,高熵 | 近零曲率,低熵 | 0.05-0.12 |
| **微颤动** | 0.3-8px 抖动 (8-12 Hz 颤动) | <0.05px 平滑或 >20px 噪声 | 0.06-0.10 |
| **速度方差** | CV >0.4 (Fitts' Law 曲线) | CV <0.15 (恒定速度) | 0.05-0.12 |
| **加加速度分析** | 高方差 (修正动作) | 低方差 (平滑函数) | 0.06 |
| **平直度指数** | 1.02-1.5 (自然曲线) | ~1.000 (尺子般笔直) | 0.04-0.10 |
| **方向熵** | >2.5 (正态分布) | <1.2 (离散角度) | 0.08 |
| **时序规律性** | 间隔可变 | >70% 相同间隔 | 0.08-0.10 |
| **瞬移** | 从不 | <10ms 内 >300px | 0.08-0.15 |
| **原点聚类** | 从不 | 点位于 (0,0) | 0.08 |
| **Bezier 检测** | 不规则加速 | >85% 恒定加速 | 0.10 |
| **时间戳格式** | 整数毫秒 | 非整数 (合成生成) | 0.10 |
| **亚像素精度** | 0-2 位小数 | >6 位小数 (Math.random) | 0.08-0.15 |
| **自相关** | 非周期性运动 | 周期性模式 (正弦波) | 0.10 |
| **运动连续性** | 暂停 >150ms (思考) | 无暂停 (连续自动化) | 0.06 |
| **速度极小值** | 修正性子运动 | 无轨迹中间修正 | 0.06 |
### 点击落点 — _权重:0.15_
人类几乎从不点击元素的精确几何中心。
| 检查项 | 人类 | 机器人 | 惩罚值 |
| ------------------ | ---------------------- | ---------------------------- | --------- |
| **中心偏移** | >5% 偏移,可变 | >70% 在中心 5% 范围内 | 0.06-0.12 |
| **偏移方差** | std >0.02 | std <0.02 (完全相同) | 0.08 |
| **点击停留时间** | 60-250ms,可变 | <10ms 或完全均匀 | 0.06-0.08 |
| **零持续时间** | 从不 | 没有 mousedown 即分发 | 0.08 |
### 点击前减速 — _权重:0.10_
Fitts' Law:人类在接近点击目标时会减速。
| 检查项 | 人类 | 机器人 |
| -------------------------- | -------------------------- | ---------------------------------------- |
| 点击前最后 500ms 的速度 | 递减 (接近阶段) | 恒定或增加 (无目标定位行为) |
### 击键动态 — _权重:0.15_
每个人都有独特的打字节奏,无法完美复制。
| 检查项 | 人类 | 机器人 | 惩罚值 |
| -------------------- | ------------------------ | ---------------------------- | --------- |
| **停留时间** | 50-200ms,可变 | <5ms 或跨键完全相同 | 0.08-0.10 |
| **飞行时间** | 30-500ms,可变 | <15ms (不可能) 或均匀 | 0.08-0.10 |
| **节奏熵** | >2.0 (自然方差) | <1.5 (机械精度) | 0.06 |
| **均匀检测** | CV >0.15 | CV <0.08 (机器人般的均匀性) | 0.08 |
### 滚动行为 — _权重:0.10_
| 检查项 | 人类 | 机器人 |
| ------------------ | ------------------------ | ---------------------------- |
| **速度方差** | 爆发 + 暂停可变 | 恒定速度 (CV <0.15) |
| **方向反转** | 频繁 (重读) | 无 (单向) |
| **暂停** | >300ms 间隔 (阅读) | 连续滚动 |
| **增量均匀性** | 滚动量可变 | 固定增量 (CV <0.05) |
### 触摸生物识别 — _权重:0.10_
真实的手指会产生压力分布和团块状的接触面积。
| 检查项 | 人类 | 机器人 |
| -------------------- | ------------------------------------ | -------------------------------------- |
| **压力变化** | CV >0.01 (手势过程中变化) | CV ~0 (恒定或缺失) |
| **接触面积** | radiusX/Y 随手指滚动而变化 | 恒定或为零 |
| **轨迹抖动** | 亚像素偏差 | RMS <0.3px (几何上完美) |
| **末端减速** | 滑动结束时的自然减速 | 恒定速度或突然停止 |
### 传感器数据 — _权重:0.10_
手持设备的人类会引入不自主的微运动。
| 检查项 | 人类 | 机器人 |
| ---------------------- | --------------------------------------- | ------------------------ |
| **加速度计噪声** | 每轴 stddev 0.01-0.5 (微振动) | 零或完全静止 |
| **陀螺仪震颤** | 可检测到 8-12 Hz 震荡 | 零旋转速率 |
| **方向漂移** | 随时间缓慢的自然漂移 | 完全固定的角度 |
| **跨轴相关性** | 轴之间的相关噪声 | 独立或为零 |
### 事件顺序 — _权重:0.05_
浏览器事件按特定顺序触发。违规表明是合成分发。
```
Expected: mousemove -> mousedown -> mouseup -> click
keydown -> keyup
touchstart -> touchmove -> touchend
```
| 异常 | 惩罚值 |
| -------------------------------------- | --------- |
| 点击前没有 mousedown/mouseup | 每次 0.02 |
| mousedown/mouseup 时间戳相同 | 0.03 |
| touchmove 前没有 touchstart | 0.02 |
| keyup 前没有 keydown | 0.02 |
### 合成事件检测 — _权重:0.15_
跨信号分析,即使单个信号看起来合理,也能捕捉到自动化框架。
| 检查项 | 人类 | 机器人 | 惩罚值 |
| ---------------------------- | ------------------ | --------------------------------- | ------ |
| **跨信号快速分发** | 正常停留时间 | 点击和按键均 <5ms | 0.10 |
| **单通道快速** | 正常停留时间 | 点击或按键 <5ms | 0.04 |
| **零时间点击对** | mousedown/mouseup 间隔 >0 | 时间戳相同 (CDP 分发) | 0.05 |
### 参与度 — _权重:0.05_
| 检查项 | 人类 | 机器人 |
| ---------------------- | ---------------------------- | --------------------- |
| **首次交互时间** | >200ms (视觉处理) | <50ms (预脚本) |
| **事件密度** | 会话持续时间内的合理值 | <500ms 内 >50 个事件 |
## 评分
```
Final Score = 1.0 - sum(category penalties)
Each category has a maximum penalty cap:
Mouse: 0.30 Click: 0.15
Pre-click: 0.10 Keystrokes: 0.15
Scroll: 0.10 Touch: 0.10
Sensors: 0.10 Event order: 0.05
Synthetic: 0.15 Engagement: 0.05
-----------------
Maximum total: 1.15 (capped at 1.00)
Score >= 0.5 -> CLEARED (human) Token issued
Score < 0.5 -> BLOCKED (bot) No token
```
阈值可通过 `scoreThreshold` 选项配置。
## 配置
| 选项 | 默认值 | 描述 |
| ---------------- | -------------- | ---------------------------- |
| `secretKey` | 随机 32 字节 | Token 签名密钥 |
| `scoreThreshold` | `0.5` | 通过的最低分数 (0.0-1.0) |
| `debug` | `false` | 在响应中包含完整分析 |
| `challengeTtl` | `60000` | 挑战过期时间 (毫秒) |
## 为什么有效
| 攻击方式 | 防御手段 |
| ------------------------------- | ---------------------------------------------------------------------- |
| Selenium/Puppeteer `moveTo()` | 产生速度均匀且曲率熵为零的直线 |
| Bezier 曲线鼠标库 | 通过恒定的二阶导数检测 (过于平滑的加速) |
| 录制的人类回放 | HMAC nonce 防止回放;时间戳不匹配 |
| 合成 `dispatchEvent()` | 缺少 mousedown/mouseup 序列;点击停留时间为零 |
| 无头触摸模拟 | 零压力,零接触半径,无抖动 |
| 模拟传感器 | 零噪声底,无跨轴相关性 |
| 快速 `sendKeys()` | 飞行时间 <15ms (物理上不可能),零停留方差 |
| WindMouse / ghost cursor | 自相关,缺少速度极小值,无运动暂停 |
| Perlin/Catmull-Rom 路径 | 亚像素精度异常,周期性自相关 |
| CDP 鼠标分发 | 零时间 mousedown/mouseup 对,跨信号快速分发 |
基本洞察:**人类运动控制受生物力学限制支配** (Fitts' Law、生理性震颤、速度-准确性权衡),这些限制在运动数据中产生特征性的统计特征。这些特征是不自主的,并且在分布层面上极难复制,即使单个数据点可以被伪造。
### 元素绑定
追踪特定交互元素上的点击精度:
```
const collector = createCollector();
collector.attach();
collector.bind(submitBtn, 'submit');
collector.bind(checkbox, 'agree');
// getData() includes:
// bc: [[offsetX, offsetY, dwell, width, height, time, index], ...]
// bl: ['submit', 'agree']
collector.unbind(submitBtn);
```
绑定元素的点击会针对中心偏移精度进行分析 —— 机器人点击精确中心,人类则不会。
## 示例
```
npm run example
# http://localhost:3002
```
## 测试
```
npm test # unit tests
npm run test:e2e # playwright e2e (10 bot algorithms)
npm run test:all # both
```
### E2E 机器人算法
E2E 测试套件通过真实浏览器运行 10 种拟人化算法,并验证全部被检测到:
| 算法 | 技术 |
| --------------- | ---------------------------------------- |
| Linear | 带有随机抖动的直线 |
Bezier | 三次 Bezier 曲线插值 |
| Sinusoidal | 具有可变振幅的正弦波路径 |
| WindMouse | Ghost Mouse 算法 (风 + 重力模型) |
| Overshoot | 目标超调并修正 |
| Perlin | 类 Perlin 噪声位移 |
| Spring-Damper | 物理弹簧模拟 |
| Gaussian Jitter | 线性路径上的高斯噪声 |
| Catmull-Rom | 穿过随机控制点的样条曲线 |
| Bell Velocity | 钟形曲线速度分布 |
在单元测试和真实浏览器 E2E 测试中,所有 10 种算法的得分均低于 0.5 (被阻止)。
## 格式化
```
npx prtfm
```
## 许可证
[MIT](LICENSE)
标签:CAPTCHA替代, GNU通用公共许可证, JavaScript库, MITM代理, Node.js, Web安全, 人机验证, 传感器数据, 击键动力学, 反欺诈, 完整性评分, 异常检测, 挑战-响应, 数据可视化, 无感验证, 机器人检测, 浏览器指纹, 生物特征识别, 自定义脚本, 蓝队分析, 行为生物识别, 零依赖, 风控系统, 鼠标轨迹分析