开放接口
任何网站和应用都可以免费调用 Cachet 的接口,生成和打开限时加密消息。通过接口生成的密文与 Cachet 输入法、官网完全互通。
| 接口地址 | http://1.12.58.194:82 |
|---|---|
| 格式 | JSON,UTF-8。所有接口都允许跨域调用(CORS),无需注册,无需密钥。 |
| 限流 | 每个 IP 每分钟:创建 60 次、读取 600 次。超出返回 429。 |
两种接入方式
| 零知识接口(推荐) | 服务端加解密接口 | |
|---|---|---|
| 接口 | POST /v1/keysGET /v1/keys/{id} | POST /v1/sealPOST /v1/open |
| 加解密在哪里做 | 你的设备 / 浏览器(用我们的 SDK) | 我们的服务器 |
| 服务器能否看到原文 | 不能 | 能(只在内存中处理,不保存、不记录) |
| 适合 | 网页、App、对隐私有要求的场景 | 后端脚本、机器人、快速对接 |
方式一:网页 SDK(零知识)
本站运行的就是这份 SDK:sdk/cachet.js(约 85 KB,未压缩,可直接审阅)。下载后放到你自己的网站,以 ES 模块引入:
import * as Cachet from './cachet.js';
// 加密:有效期 1 小时,可选访问密码、阅后即焚
const { ciphertext, expiresAt } = await Cachet.seal('今晚八点老地方见', {
ttl: 3600, // 秒,30 秒 ~ 30 天
password: '', // 可选
burn: false, // true = 只能打开一次
style: 'han', // 'han' | 'latin' | 'invisible'
server: 'http://1.12.58.194:82',
});
// 解密:可以传入整段聊天文字,SDK 会自己找出密文
try {
const { text, expiresAt, reads } = await Cachet.open(ciphertext, { password: '' });
} catch (e) {
// e.code: not_cachet | password_required | wrong_password | expired | consumed | not_found
}
// 只检查、不打开(不消耗打开次数)
const info = Cachet.inspect(ciphertext); // { hasPassword, burn, ... } 或 null
方式一:零知识接口
如果你在其他语言里自己实现加解密,只需要这两个接口;加密算法见 协议。
POST /v1/keys:创建钥匙
curl -X POST BASE/v1/keys -H 'Content-Type: application/json' \
-d '{"ttl": 3600, "maxReads": 0}'
| 参数 | 说明 |
|---|---|
ttl | 有效期,秒。30 ~ 2592000(30 天) |
maxReads | 最多可读取次数,0 表示不限。阅后即焚填 1,上限 100 |
201 {"id": "Rk2x…", "key": "Q4m…", "expiresAt": 1791900000, "now": 1791896400}
id 和 key 都是不带填充的 base64url,id 解码后 12 字节,key 32 字节。
GET /v1/keys/{id}:读取钥匙
curl BASE/v1/keys/Rk2x…
200 {"key": "Q4m…", "expiresAt": 1791900000, "now": 1791896500, "reads": 1, "maxReads": 0}
410 {"error": "expired", "expiresAt": …} 已过期,钥匙已抹除
410 {"error": "consumed", "reads": 1, …} 阅后即焚,已被读取
404 {"error": "not_found"}
每次成功读取都会计数;达到 maxReads 的那一次读取之后,钥匙立即抹除。
方式二:服务端加解密接口
这两个接口由服务器完成加解密,服务器会在内存中短暂处理原文。我们不保存、不记录,但如果你的场景不允许任何第三方经手原文,请使用方式一。
POST /v1/seal:加密
curl -X POST BASE/v1/seal -H 'Content-Type: application/json' \
-d '{"text": "今晚八点老地方见", "ttl": 3600, "password": "", "burn": false, "style": "han"}'
201 {"ciphertext": "「…」", "id": "Rk2x…", "expiresAt": 1791900000, "now": 1791896400}
text 最多 10000 字;style 可选 han(默认,最短)、latin、invisible。
POST /v1/open:解密
curl -X POST BASE/v1/open -H 'Content-Type: application/json' \
-d '{"ciphertext": "朋友发来的:「…」", "password": ""}'
200 {"text": "今晚八点老地方见", "expiresAt": 1791900000, "now": …, "reads": 1}
401 {"error": "password_required"}
403 {"error": "wrong_password"} 密码错误不计入打开次数
410 {"error": "expired" | "consumed"}
422 {"error": "not_cachet" | "private_message"}
协议
如果你想用其他语言实现零知识加解密,这是完整的格式。安卓、服务器、网页三端用同一组测试向量逐字节核对过。
密文字节 = 0x18 ‖ flags(1) ‖ keyId(12) ‖ AES-256-GCM(K, nonce = 12 个 0x00, aad = 前 14 字节, 明文) flags: bit0 = 有访问密码,bit1 = 阅后即焚 K = HKDF-SHA256(ikm = key, salt = keyId, info = "cachet v2 public", 32) 有密码时: ikm = key ‖ PBKDF2-SHA256(UTF-8(密码), salt = keyId, 100000 次, 32) 明文 = 1 字节格式 ‖ 内容;0x00 UTF-8,0x01 UTF-16BE,0x02 raw DEFLATE(UTF-8)
密文字节再转写成文字:
- 汉字:
「+ 3755 进制数字 +」,数字对应 GB2312 一级汉字(按编码顺序);转换前在字节前加一个0x01。 - 字母:
✉+ 62 进制数字(0-9A-Za-z),同样先加0x01。 - 隐形:
🔏+ 每字节一个 Unicode 变体选择符(<16 用 U+FE00+b,否则 U+E0100+b−16)。