Chingtsi CTPRooF
v1 · 已上线

CTPRooF 人机验证:一次验证,一次票据

CTPRooF 为登录、注册、提交、投票、领券等表单场景提供免交互的人机验证。前端完成工作量证明拿到一次性票据,服务端核销票据后才放行——脚本批量请求与票据重放都会被拒绝。

服务端点:https://www.chingtsi.com/ctproof-api · 客户端:https://www.chingtsi.com/ctproof.js

无需用户点击

不弹拼图、不要求识图,正常用户无感通过;难度自适应,弱设备同样可用。

一次性票据

每次验证签发独立 ticket,核销一次即失效,重放会返回 timeout-or-duplicate。

服务端为准

前端结果不可信。是否放行以接入方服务端调用 siteverify 的返回值为准。

两步接入

前端引入脚本接管表单,服务端核销票据。整套流程约 10 行代码。

第一步 · 前端

<script src="https://www.chingtsi.com/ctproof.js" data-ctproof-site="你的 site_key" defer></script>

<form action="/login" method="post" data-ctproof>
  <!-- 原有字段保持不变 -->
  <div data-ctproof-state></div>
</form>

脚本接管所有带 data-ctproof 的表单:提交时自动完成挑战计算,成功后在表单内注入隐藏字段 ctproof-ticket 并继续提交;失败则停留在当前页并提示。[data-ctproof-state] 用于展示「正在验证 / 验证通过 / 失败」。

第二步 · 服务端核销

<?php
$ticket = trim((string)($_POST['ctproof-ticket'] ?? ''));
$res = file_get_contents('https://www.chingtsi.com/ctproof-api?action=siteverify', false, stream_context_create([
  'http' => [
    'method'  => 'POST',
    'header'  => "Content-Type: application/x-www-form-urlencoded\r\n",
    'content' => http_build_query([
      'site_key' => '你的 site_key',
      'secret'   => '你的 secret',
      'ticket'   => $ticket,
    ]),
    'timeout' => 5,
  ],
]));
$r = json_decode((string)$res, true);
if (empty($r['success'])) {
  exit('人机验证未通过:' . ($r['error'] ?? 'unknown'));
}
// 通过,继续业务逻辑
const res = await fetch('https://www.chingtsi.com/ctproof-api?action=siteverify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    site_key: '你的 site_key',
    secret:   '你的 secret',
    ticket:   req.body['ctproof-ticket'] || '',
  }),
});
const r = await res.json();
if (!r.success) return res.status(400).send('人机验证未通过:' + (r.error || 'unknown'));
// 通过,继续业务逻辑
import requests

r = requests.post('https://www.chingtsi.com/ctproof-api?action=siteverify', data={
    'site_key': '你的 site_key',
    'secret':   '你的 secret',
    'ticket':   request.form.get('ctproof-ticket', ''),
}, timeout=5).json()

if not r.get('success'):
    abort(400, '人机验证未通过:' + str(r.get('error')))
# 通过,继续业务逻辑
curl -s -X POST 'https://www.chingtsi.com/ctproof-api?action=siteverify' \
  -d 'site_key=你的 site_key' \
  -d 'secret=你的 secret' \
  -d 'ticket=前端拿到的 ticket'

纯接口接入(不使用组件)

若你的客户端不是浏览器页面(小程序、App、服务端渲染),可直接走三步接口。挑战与票据均由服务端签发,走过期时间。

# 1) 取挑战(GET,需带 Origin 或 Referer 以校验来源域名)
GET /ctproof-api?action=challenge&site_key=你的site_key
→ {"ok":true,"cid":"...","nonce":"...","difficulty":3,"expires":1767...,"sig":"...","ttl":120}

# 2) 计算工作量证明:找整数 n,使 sha256(nonce + ":" + n) 的十六进制前缀含 difficulty 个 0
POST /ctproof-api?action=answer
  site_key, cid, nonce, difficulty, expires, sig, answer=n
→ {"ok":true,"ticket":"32位十六进制","ttl":180}

# 3) 服务端核销(一次性)
POST /ctproof-api?action=siteverify
  site_key, secret, ticket
→ {"ok":true,"success":true,"error-codes":[],"challenge_ts":"...","hostname":"你的域名","site_key":"..."}

挑战带 HMAC 签名,参数被篡改会被拒绝;同一挑战不能重复作答;票据核销一次即失效。默认工作量难度 3(约数千次哈希,通常 100ms 内完成)。

归属校验(域名所有权)

申请通过后,后台会为站点签发 site_key 与 secret。为防止他人冒用你的域名接入,后台可对站点做一次归属校验,三选一即可,通过后站点标记为「已校验」。

方式需要做什么校验依据
DNS TXT 在域名 DNS 添加一条 TXT 记录:_ctproof-verify.你的域名 → ctp-site-key=你的site_key 服务器直接查询 DNS 记录,不依赖站点能否被抓取,最稳
well-known 在站点根目录放置 /.well-known/ctproof.txt,内容包含你的 site_key 服务器抓取该文件并与 site_key 比对
meta 在首页 <head> 加 <meta name="ctproof-site" content="你的site_key"> 服务器抓取首页并匹配 meta 内容

校验只影响后台的「已校验」标记,不阻断接口调用;域名白名单以申请时登记的 site_url 为准,来源域名不一致会返回 domain-mismatch。若站点未启用 HTTPS 或服务器出网受限,请优先使用 DNS TXT 方式。

错误码

error-code含义处理建议
missing-input-secret未提交 secret检查服务端参数
invalid-input-secretsecret 与站点不匹配(可能已被重置)在后台重置后同步新 secret
missing-input-response未提交 ticket确认前端注入成功
invalid-input-response票据不存在或格式错误票据需原样透传
timeout-or-duplicate票据已过期或已被核销不可重试同一票据,需重新验证
invalid-input-sitekeysite_key 不存在核对 site_key
domain-mismatch调用来源域名与登记域名不一致检查页面域名白名单
bad-proof工作量证明不成立按 difficulty 重新计算
replay同一挑战重复作答每次重新取挑战
site-disabled该站点已被管理员停用联系管理员
rate-limited请求过于频繁退避重试

在线演示

下面这段是真实调用:点击后前端完成一次验证拿到票据,再提交到本站服务端核销。连续点击第二次会看到「票据已被使用」——这就是防重放。

演示站点:ctp_pub_DEMO0000000000000001,登记域名 chingtsi.com。生产接入请先申请,获得独立的 site_key 与 secret。

开始接入

提交站点与应用场景,审核通过后发放 site_key 与 secret。

申请接入 →