浏览文档目录

联享券

请求加解密教程

使用 P-256 ECDH、HKDF-SHA256 和 AES-256-GCM,在 HTTPS 内再次加密联享券的路径、Token、请求体与响应体。

P-256 ECDH

每个会话使用客户端临时密钥与服务端当前公钥协商共享秘密。

HKDF-SHA256

按品牌、来源、会话、通道和方向派生独立密钥。

AES-256-GCM

请求和响应分别认证加密,篡改会导致解密失败。

何时启用

生产服务端调用建议使用 required 模式。即使 TLS 在网关或代理处终止,业务参数仍只会在安全传输客户端和 IM 网关内以明文出现。联享券测试系统的“接口测试”页可以切换应用层加密,直接比较两种请求结果。

required 模式握手或解密失败时直接失败。建立过安全会话后,不允许因服务配置变化静默降级为明文。

下载服务端接入示例

示例与当前网关协议同版本,只能放在商家服务端项目中,不是 App、Android 或网页客户端代码,也不要提交 sop_ Token。

mkdir -p secure-transport
curl -fsSLO "$SITE/examples/secure-transport/index.js" \
  --output-dir secure-transport
curl -fsSLO "$SITE/examples/secure-transport/client.js" \
  --output-dir secure-transport

需要 Node.js 20 或提供 Fetch、Web Crypto、btoa 和 atob 的等价运行时。下载的 client.js 会从同目录导入 index.js。应用策略不是 * 时,将 IM_REQUEST_ORIGIN 设置为白名单允许的完整来源;客户端会把它绑定到加密会话并发送 Origin 头。

Java ZIP 是可直接编译的商家服务端 Maven 项目,包含安全传输调用库、连接检查、发券示例和运行说明,需要 JDK 17+。

完整发券示例

import { SecureTransportClient } from './secure-transport/client.js'

const API_ORIGIN = 'https://api.example.com'
const client = new SecureTransportClient({
  baseUrl: `${API_ORIGIN}/api/v1`,
  mode: 'required',
  pageOrigin: process.env.IM_REQUEST_ORIGIN || '',
  timeoutMs: 15_000,
})

const externalIssueId = 'points_order_20260909_001'
const idempotencyKey = `issue:${externalIssueId}`
const result = await client.dispatch({
  method: 'POST',
  path: '/api/v1/open-platform/vouchers/issue',
  query: {},
  headers: {
    accept: 'application/json',
    authorization: `Bearer ${process.env.IM_OPEN_PLATFORM_TOKEN}`,
    'content-type': 'application/json',
    'idempotency-key': idempotencyKey,
  },
  body: {
    template_id: process.env.IM_POINTS_TEMPLATE_ID,
    external_issue_id: externalIssueId,
    recipient_user_id: 'AUTHORIZED_SOCHAT_USER_ID',
  },
})

if (!result.secure) throw new Error('安全传输未建立')
if (result.status < 200 || result.status >= 300 || result.body?.success === false) {
  throw new Error(result.body?.message || `IM API ${result.status}`)
}
console.log(result.body.data)
安全分发接口外层成功时 HTTP 状态为 200。真实业务状态在解密后的 result.status,统一业务响应在 result.body;两层都必须检查。

协议流程

  1. 1. Bootstrap GET /api/v1/secure/bootstrap,校验 v、suite、enabled、kid 和 serverPublicKey,并用 serverTime 校准本地时间。
  2. 2. Session 生成临时 P-256 密钥对,POST /api/v1/secure/session 发送公钥;保存 sid、salt、brand、origin 和 expiresAt。
  3. 3. Derive 执行 ECDH,再用 HKDF 分别派生 http/c2s 与 http/s2c 两把 256 位密钥。
  4. 4. Encrypt 为每次请求生成新的 rid 和 96 位 IV,把 method、path、query、headers、body 整体加密。
  5. 5. Dispatch POST /api/v1/secure/dispatch。服务端校验时间、会话、来源和 rid 防重放,解密后执行逻辑接口。
  6. 6. Decrypt 使用同一 rid 和 s2c AAD 验证并解密响应,再按内层 status 处理业务结果。

网络上看到的请求

POST /api/v1/secure/dispatch
Content-Type: application/vnd.sochat.secure+json

{
  "v": 1,
  "suite": "P256-HKDF-SHA256-A256GCM",
  "kid": "2026-09",
  "sid": "st_...",
  "rid": "rid_...",
  "ts": 1788912000000,
  "iv": "base64url-96-bit-iv",
  "ciphertext": "base64url-ciphertext-and-128-bit-tag"
}

authorization、逻辑接口路径、幂等键和业务 JSON 都在 ciphertext 内。外层请求只携带协议字段和链路追踪允许头。

密码学参数

项目规则
suiteP256-HKDF-SHA256-A256GCM
HKDF infosoim|v1|{brand}|{origin 或 -}|{sid}|http|{c2s 或 s2c}
AADsoim|v1|http|{c2s|s2c}|{kid}|{sid}|{rid}|{ts}
IV / tag每次随机 96 位 IV;128 位 GCM 认证标签随 ciphertext 编码。
encoding所有二进制字段使用无填充 base64url。
clock / session默认允许 60 秒时钟偏差,会话默认 600 秒;客户端在到期前 5 秒续期。

会话、重试与幂等

  • SECURE_SESSION_EXPIRED 或 SECURE_SESSION_MISMATCH 时清理会话、重新协商并且只重试一次。
  • 重试会生成新的 rid 和 IV,但业务 Idempotency-Key、external_issue_id 和请求体必须保持不变。
  • 每个 rid 在会话内只能使用一次;重复发送同一密文会返回 REQUEST_REPLAYED。
  • 并发请求各自保留会话密钥副本,完成后清零;不要把会话密钥写入日志或持久化。

常见安全传输错误

安全传输错误发生在业务接口之前。业务 4xx 会正常加密返回,并出现在解密后的 result.status 和 result.body。

SECURE_TRANSPORT_REQUIRED · 426网关要求应用层加密,但请求走了普通业务地址。
REQUEST_TIMESTAMP_OUT_OF_RANGE客户端时间偏差过大;重新读取 bootstrap.serverTime。
SECURE_SESSION_EXPIRED会话过期或服务端已清理;重新协商后重试一次。
SECURE_SESSION_ORIGIN_MISMATCH创建会话和分发请求的 Origin 不一致。Node 服务端通常都使用空 origin。
DECRYPTION_FAILED密钥、AAD、IV、密文或认证标签不匹配。
REQUEST_REPLAYED同一 sid 与 rid 已经处理过,必须生成新的 rid。

上线检查

  • sop_ Token 只放在内层 authorization 头和可信服务端环境变量中。
  • 逻辑 path 必须以 /api/v1/ 开头,不能包含 query、hash、反斜杠或 /secure/;查询参数放在 query 对象。
  • 加密分发只支持 JSON。二进制、流式响应和大于服务端上限的信封应走对应直连接口。
  • 记录 inner status、业务 requestId 和安全错误码,禁止记录 Token、券码、明文密钥、salt 或完整 ciphertext。