跳到正文
← API 文档

Specification

INTD 数据包格式 v1

格式公开是有意为之:机密性和完整性来自对请求的加密和签名,而不是来自对布局的保密。固定结构和 CRC32 让服务器能在写入存储之前低成本地丢弃垃圾数据。

Stable Big-endian CRC-32/IEEE X25519 · HKDF · AES-256-GCM

容器

偏移大小字段取值
0 4 MAGIC 0x49 0x4E 0x54 0x44(「INTD」)
4 1 VERSION 0x01
5 1 FLAGS 0x00,保留
6 8 TIMESTAMP uint64 BE,unix 毫秒 —— 增量的基准
14 4 EVENTS_COUNT uint32 BE
18 4 BLOB_LENGTH uint32 BE,EVENTS_BLOB 的长度
22 N EVENTS_BLOB 连续排列的事件
22+N 4 CRC32 uint32 BE,EVENTS_BLOB 的 CRC-32/IEEE

总大小恰好为 26 + N 字节;多余字节视为错误。

事件

大小字段取值
1 TYPE 0x01 pageview,0x02 search
4 TS_DELTA uint32 BE,TIMESTAMP 之后的毫秒数
2 + var DOMAIN uint16 长度 ≤ 255,UTF-8
2 + var URL uint16 长度 ≤ 2048,UTF-8
2 + var QUERY uint16 长度 ≤ 500,没有时为 0
1 + var LANG uint8 长度 ≤ 20,BCP 47
1 DEVICE_TYPE 0x00 unknown,0x01 desktop,0x02 mobile,0x03 tablet,0x04 bot

字符串按 UTF-8 字符边界截断。若长度超出限制、字符串不是合法 UTF-8、事件数与头部不符,或 CRC32 不匹配,服务器将拒绝该数据包。

服务器会做什么

  • 域名由 URL 重新计算(eTLD+1),不采用客户端提供的值。
  • 从 URL 和查询中剥离令牌、密码、电子邮件和卡号。
  • 丢弃超过 7 天的事件,以及来自未来(超过 10 分钟)的事件。
  • 国家按连接 IP 解析,安装标识符针对每个扩展单独哈希。

加密与签名

envelope
sessionKey   = random(32)
iv           = random(12)
ciphertext   = AES-256-GCM(sessionKey, iv, bucket)
ephemeral    = X25519 —— 每个数据包一对新密钥
shared       = X25519(ephemeral.private, extension.publicKey)
derivedKey   = HKDF-SHA256(IKM = shared, salt = iv, info = "intentd-bucket-v1", 32)
encryptedKey = AES-256-GCM(derivedKey, iv, sessionKey)   // 48 字节
signature
X-Signature = hex(HMAC-SHA256(secret,
  "intentd-v2|bucket|" + X-Timestamp + "|" + X-API-Key + "|" + hex(SHA-256(body))))

该方案在扩展私钥日后遭泄露时不提供前向保密,也无法防范持有打包副本的人:针对重新打包的防护来自按安装划分的会话、扩展 ID 校验和服务端校验。

测试向量

bucket (hex)
// 一条 search 事件:基准 1758000000000,增量 7,
// 域名 a.io,URL https://a.io/?q=x,查询 x,语言 en,desktop
494e5444 01 00 0000019950f72c00 00000001 00000025
02 00000007 0004 612e696f 0011 68747470733a2f2f612e696f2f3f713d78 0001 78 02 656e 01
cee33ee2
hkdf
HKDF-SHA256(IKM = 0x11 × 32, salt = 0x22 × 12, info = "intentd-bucket-v1", L = 32)
=> 4a359b8818fc073f6f81a3cb4acd9065302e285fbef0d35d2ff71fba107e6056