Skip to content

协议规范

本文定义 HoldFi OpenAPI 与第三方授权码换身份接口共用的服务间安全协议。OAuth2 浏览器跳转、HoldFi portal_url 和第三方 return_url 不使用本协议。

协议版本由逻辑路径中的 /v1 表达。算法、编码和签名原文均为固定契约,不支持调用方自行协商。

1. HTTP 约定

服务间接口统一使用:

http
POST {api_path}
Content-Type: application/json; charset=UTF-8

生产环境必须使用 HTTPS,并正常校验服务端证书和域名。应用层加密不能替代 TLS。

1.1 逻辑 API path

签名中的 api_path 是双方登记的逻辑接口路径:

  1. HoldFi 对外接口以 /openapi/v1/ 开头;第三方授权码换身份接口使用双方登记的第三方逻辑路径。两者都不包含域名、端口、query string、fragment、网关 stage 或部署前缀。
  2. 不允许尾部 /、重复 //、反斜杠、... 或百分号编码路径。
  3. 本协议接口不允许 query 参数,业务条件必须放在加密业务体中。
  4. 调用第三方授权码换身份接口时,HTTP URL 与逻辑 API path 分别登记;参与签名的是逻辑 API path。

1.2 HTTP 状态与安全响应边界

HoldFi 已识别 platform 且具备响应密钥时,无论业务成功或失败都返回 HTTP 200,实际结果由安全响应中的 code 表达。

无法建立安全响应时返回最小明文错误:

场景HTTP 状态明文响应
外层 JSON 无法解析、缺少 platform 或平台未登记400{"code":400000,"msg":"PROTOCOL_REQUEST_REJECTED"}
缺少响应密钥或密码服务不可用503{"code":503000,"msg":"SECURE_RESPONSE_UNAVAILABLE"}

调用方不得把最小明文错误当作业务响应。网络、TLS、网关或未知路径错误也可能返回其他 HTTP 状态。

2. 外层报文

2.1 请求

外层 JSON 必须严格包含以下四个字段,不允许缺失、重复或增加字段:

字段类型约束
platformString最长 64 个字符,区分大小写
timestampIntegerUnix 毫秒时间戳
payloadString加密信封 UTF-8 字节的标准 Base64
signatureStringRSA 请求签名的标准 Base64
json
{
  "platform": "partner_app",
  "timestamp": 1787803200000,
  "payload": "BASE64_ENCRYPTED_ENVELOPE",
  "signature": "BASE64_REQUEST_SIGNATURE"
}

platform 必须匹配 ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$。服务端不执行 trim 或大小写转换。

2.2 响应

标准安全响应必须严格包含以下六个字段:

字段类型约束
platformString原样回显请求平台
timestampInteger响应生成时的 Unix 毫秒时间戳
codeInteger0999999200 表示成功
msgString最长 512 个 Unicode 字符,不允许 CR/LF
dataString加密信封 UTF-8 字节的标准 Base64
signatureStringRSA 响应签名的标准 Base64
json
{
  "platform": "partner_app",
  "timestamp": 1787803200123,
  "code": 200,
  "msg": "success",
  "data": "BASE64_ENCRYPTED_ENVELOPE",
  "signature": "BASE64_RESPONSE_SIGNATURE"
}

没有业务数据时仍加密空对象 {},不得省略 data 或返回空字符串。

2.3 长度限制

对象上限
完整 HTTP 请求体350000 字节
解密后的业务 JSON65535 UTF-8 字节
外层 payloaddata262144 个 Base64 字符

3. 密钥关系

每个开放应用、每个环境使用两对独立的 RSA 密钥:

密钥对私钥持有方公钥持有方用途
第三方密钥对仅第三方第三方、HoldFi第三方签名和解密;HoldFi 验签和加密
HoldFi 密钥对仅 HoldFiHoldFi、第三方HoldFi 签名和解密;第三方验签和加密

调用规则:

调用方向数据使用谁的公钥加密使用谁的私钥签名
第三方请求 HoldFiHoldFi 公钥第三方私钥
HoldFi 响应第三方第三方公钥HoldFi 私钥
HoldFi 请求第三方换码第三方公钥HoldFi 私钥
第三方响应 HoldFiHoldFi 公钥第三方私钥

RSA 规格:

  • 模数长度:3072 位。
  • 公钥指数:65537。
  • 交换格式:X.509 SubjectPublicKeyInfo PEM,即 BEGIN PUBLIC KEY
  • 不接受 PKCS#1 BEGIN RSA PUBLIC KEY、JWK 或 OpenSSH 公钥。

私钥不得交换。测试、联调和生产环境必须使用不同密钥。线上报文不包含 key_id,轮换密钥时双方需同步切换,旧密钥生成的完整报文随即失效。

4. 固定算法

当前协议只允许下表参数,不支持算法协商:

用途固定值精确参数
业务数据加密A256GCMAES-256-GCM;32 字节随机 AES 密钥;12 字节随机 IV;16 字节认证 Tag;AAD 为空
AES 密钥加密RSA-OAEP-256RSA-3072;OAEP Hash=SHA-256;MGF=MGF1(SHA-256);Label 为空
数字签名RS256RSASSA-PKCS1-v1_5 + SHA-256;Java 名称为 SHA256withRSA;不是 RSA-PSS
字符编码UTF-8所有签名原文、业务 JSON 和信封 JSON 均使用 UTF-8
二进制编码标准 Base64RFC 4648 标准字母表;保留编码所需的 = padding;禁止空白和换行;不使用 Base64URL
随机数密码学安全随机源每次请求和每次响应重新生成 AES 密钥与 IV,禁止复用或自行派生

RSA-OAEP-256A256GCMRS256 使用 JWA 的标准算法名称,但本文定义的是 DataId 自有加密信封,不是 JWE/JWS 序列化格式。实现 RSA-OAEP 时必须显式设置 OAEP 与 MGF1 的 Hash 都为 SHA-256。部分 Java 运行库即使算法名包含 SHA-256,MGF1 仍可能默认使用 SHA-1,因此不得依赖运行库默认值。

5. 加密信封

payloaddata 都是下列加密信封 JSON 的 UTF-8 字节再进行标准 Base64 编码后的字符串:

json
{
  "alg": "RSA-OAEP-256",
  "enc": "A256GCM",
  "encrypted_key": "BASE64_RSA_ENCRYPTED_AES_KEY",
  "iv": "BASE64_12_BYTE_IV",
  "cipher_text": "BASE64_AES_GCM_CIPHER_TEXT",
  "tag": "BASE64_16_BYTE_GCM_TAG"
}

信封只允许以上六个字段,且都必须是 JSON String;字段顺序不影响解析。

5.1 业务 JSON

  1. 顶层必须是 JSON object,不得是数组、标量或 null
  2. 不允许重复 key;具体接口还会拒绝未知业务字段。
  3. 无参数或无数据时使用 {}
  4. 金额使用普通十进制 JSON String,不使用 JSON Number 或科学计数法。
  5. 字段缺失、null 和空字符串具有不同语义。
  6. 业务 JSON 不要求字段排序,因为签名覆盖外层原始密文字符串。

5.2 发送方加密

发送方必须按以下顺序生成 payloaddata

  1. 将业务 JSON object 序列化为 UTF-8 字节。
  2. 使用密码学安全随机源生成 32 字节 AES 密钥。
  3. 使用密码学安全随机源生成 12 字节 IV。
  4. 使用 AES-256-GCM 加密业务 JSON,AAD 传空字节;将输出拆为密文和最后 16 字节 Tag。
  5. 使用接收方 RSA 公钥按 RSA-OAEP-256 加密 AES 密钥。
  6. encrypted_keyivcipher_texttag 分别进行标准 Base64 编码。
  7. 构造加密信封 JSON,并编码为 UTF-8 字节。
  8. 对完整信封字节进行标准 Base64 编码,得到外层 payloaddata

严禁复用 AES 密钥或 IV,严禁使用 timestamp、业务单号、UUID、密码或固定字符串生成 AES 密钥或 IV。

5.3 接收方解密

接收方必须按以下顺序解密:

  1. 严格 Base64 解码外层 payloaddata
  2. 按 UTF-8 解析加密信封 JSON,拒绝缺失、重复或未知字段。
  3. 校验 alg=RSA-OAEP-256enc=A256GCM
  4. 严格 Base64 解码四个二进制字段。
  5. 校验 RSA 密文为 384 字节、IV 为 12 字节、Tag 为 16 字节。
  6. 使用接收方 RSA 私钥解密 encrypted_key,结果必须是 32 字节 AES 密钥。
  7. 使用 AES-256-GCM、空 AAD 解密 cipher_text + tag;Tag 校验失败必须终止处理。
  8. 按 UTF-8 解析业务 JSON,校验顶层为 object 后进入业务参数校验。

任何解码、算法、长度、RSA-OAEP、GCM Tag 或 JSON 校验失败,都必须按解密失败处理,不得返回部分明文,也不得继续业务逻辑。

6. 数字签名

通用规则:

  • 签名原文使用 UTF-8 编码。
  • 字段之间只插入单个 LF 字符 \n(字节 0x0A),不使用 CRLF。
  • 原文结尾没有额外换行。
  • 签名使用发送方 RSA 私钥执行 RS256(Java SHA256withRSA)。
  • 签名结果使用标准 Base64 编码。
  • 验签必须使用发送方公钥,并使用收到的原始字段值构造原文。

6.1 请求签名原文

text
api_path + "\n" + platform + "\n" + timestamp + "\n" + payload

示例:

text
/openapi/v1/users/register
partner_app
1787803200000
BASE64_ENCRYPTED_ENVELOPE

6.2 响应签名原文

text
api_path + "\n" + platform + "\n" + timestamp + "\n" + code + "\n" + msg + "\n" + data

timestampcode 使用 JSON Integer 的无前导零十进制形式;禁止小数、指数形式和前导 +

7. 被调用方校验顺序

HoldFi 与第三方提供的服务端接口均按以下顺序处理请求:

  1. 校验 HTTP 方法、Content-Type 和请求体大小。
  2. 严格解析外层 JSON,并校验字段、类型、长度、platformtimestamp 格式。
  3. 根据 platform 获取应用配置、调用方公钥和本方私钥,并检查应用状态。
  4. 使用请求来源 IP 校验当前应用的白名单。
  5. 校验时间窗口。
  6. 使用调用方公钥验证请求签名。
  7. 验签成功后才解析信封并执行 RSA-OAEP 与 AES-GCM 解密。
  8. 严格解析业务 JSON 并进入接口参数校验。

验签失败的请求不再进行后续解密。调用方处理响应时,也应先校验外层结构、platform 与时间窗口,再验签,最后解密 data

8. 时间窗口、重放与重试

请求和响应相对接收方当前时间允许前后偏差 3 分钟:

text
abs(now_ms - timestamp) <= 180000

双方服务器应保持可靠时间同步。

当前协议没有通用 noncerequest_id。不同接口的重复请求处理如下:

接口类型重复处理
用户登记platform + open_id 幂等
用户权益、账户信息、充值确认查询只读,可安全重试
门户会话创建HoldFi 对完全相同的外层请求做 6 分钟重放拦截,重复请求返回 409002

网络超时后重试时,建议重新生成 AES 密钥、IV、timestamppayloadsignature。门户会话是否创建成功不明确时,不应依赖重复发送同一密文获取原结果;应生成新请求创建新会话,旧会话会在短期内自然过期。

9. 严格 JSON 与字段规则

  1. 外层报文、加密信封和业务对象均拒绝重复 key。
  2. 不执行字符串、整数、布尔值之间的自动类型转换。
  3. 各业务接口只允许文档列出的字段,未知字段返回 400001
  4. 所有业务字段使用 snake_case。
  5. 时间统一使用 Unix 毫秒时间戳。
  6. 常量值使用全大写下划线形式。

公开业务码与含义见 常量与错误码

10. 联调检查清单

  1. 双方确认环境、platform、HTTP URL 和逻辑 API path。
  2. 确认双方公钥均为 RSA-3072 X.509 PEM,且与运行时私钥匹配。
  3. 先使用固定业务 JSON 验证加密、解密、签名和验签,再调用真实接口。
  4. 确认使用标准 Base64,不是 Base64URL。
  5. 显式确认 OAEP Hash 与 MGF1 Hash 都是 SHA-256。
  6. 确认签名原文使用 LF 且末尾无换行。
  7. 确认服务器时钟误差小于 3 分钟,出口 IP 已加入白名单。