外观
协议规范
本文定义 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 是双方登记的逻辑接口路径:
- HoldFi 对外接口以
/openapi/v1/开头;第三方授权码换身份接口使用双方登记的第三方逻辑路径。两者都不包含域名、端口、query string、fragment、网关 stage 或部署前缀。 - 不允许尾部
/、重复//、反斜杠、.、..或百分号编码路径。 - 本协议接口不允许 query 参数,业务条件必须放在加密业务体中。
- 调用第三方授权码换身份接口时,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 必须严格包含以下四个字段,不允许缺失、重复或增加字段:
| 字段 | 类型 | 约束 |
|---|---|---|
platform | String | 最长 64 个字符,区分大小写 |
timestamp | Integer | Unix 毫秒时间戳 |
payload | String | 加密信封 UTF-8 字节的标准 Base64 |
signature | String | RSA 请求签名的标准 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 响应
标准安全响应必须严格包含以下六个字段:
| 字段 | 类型 | 约束 |
|---|---|---|
platform | String | 原样回显请求平台 |
timestamp | Integer | 响应生成时的 Unix 毫秒时间戳 |
code | Integer | 0~999999,200 表示成功 |
msg | String | 最长 512 个 Unicode 字符,不允许 CR/LF |
data | String | 加密信封 UTF-8 字节的标准 Base64 |
signature | String | RSA 响应签名的标准 Base64 |
json
{
"platform": "partner_app",
"timestamp": 1787803200123,
"code": 200,
"msg": "success",
"data": "BASE64_ENCRYPTED_ENVELOPE",
"signature": "BASE64_RESPONSE_SIGNATURE"
}没有业务数据时仍加密空对象 {},不得省略 data 或返回空字符串。
2.3 长度限制
| 对象 | 上限 |
|---|---|
| 完整 HTTP 请求体 | 350000 字节 |
| 解密后的业务 JSON | 65535 UTF-8 字节 |
外层 payload 或 data | 262144 个 Base64 字符 |
3. 密钥关系
每个开放应用、每个环境使用两对独立的 RSA 密钥:
| 密钥对 | 私钥持有方 | 公钥持有方 | 用途 |
|---|---|---|---|
| 第三方密钥对 | 仅第三方 | 第三方、HoldFi | 第三方签名和解密;HoldFi 验签和加密 |
| HoldFi 密钥对 | 仅 HoldFi | HoldFi、第三方 | HoldFi 签名和解密;第三方验签和加密 |
调用规则:
| 调用方向 | 数据使用谁的公钥加密 | 使用谁的私钥签名 |
|---|---|---|
| 第三方请求 HoldFi | HoldFi 公钥 | 第三方私钥 |
| HoldFi 响应第三方 | 第三方公钥 | HoldFi 私钥 |
| HoldFi 请求第三方换码 | 第三方公钥 | HoldFi 私钥 |
| 第三方响应 HoldFi | HoldFi 公钥 | 第三方私钥 |
RSA 规格:
- 模数长度:3072 位。
- 公钥指数:65537。
- 交换格式:X.509 SubjectPublicKeyInfo PEM,即
BEGIN PUBLIC KEY。 - 不接受 PKCS#1
BEGIN RSA PUBLIC KEY、JWK 或 OpenSSH 公钥。
私钥不得交换。测试、联调和生产环境必须使用不同密钥。线上报文不包含 key_id,轮换密钥时双方需同步切换,旧密钥生成的完整报文随即失效。
4. 固定算法
当前协议只允许下表参数,不支持算法协商:
| 用途 | 固定值 | 精确参数 |
|---|---|---|
| 业务数据加密 | A256GCM | AES-256-GCM;32 字节随机 AES 密钥;12 字节随机 IV;16 字节认证 Tag;AAD 为空 |
| AES 密钥加密 | RSA-OAEP-256 | RSA-3072;OAEP Hash=SHA-256;MGF=MGF1(SHA-256);Label 为空 |
| 数字签名 | RS256 | RSASSA-PKCS1-v1_5 + SHA-256;Java 名称为 SHA256withRSA;不是 RSA-PSS |
| 字符编码 | UTF-8 | 所有签名原文、业务 JSON 和信封 JSON 均使用 UTF-8 |
| 二进制编码 | 标准 Base64 | RFC 4648 标准字母表;保留编码所需的 = padding;禁止空白和换行;不使用 Base64URL |
| 随机数 | 密码学安全随机源 | 每次请求和每次响应重新生成 AES 密钥与 IV,禁止复用或自行派生 |
RSA-OAEP-256、A256GCM 和 RS256 使用 JWA 的标准算法名称,但本文定义的是 DataId 自有加密信封,不是 JWE/JWS 序列化格式。实现 RSA-OAEP 时必须显式设置 OAEP 与 MGF1 的 Hash 都为 SHA-256。部分 Java 运行库即使算法名包含 SHA-256,MGF1 仍可能默认使用 SHA-1,因此不得依赖运行库默认值。
5. 加密信封
payload 和 data 都是下列加密信封 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
- 顶层必须是 JSON object,不得是数组、标量或
null。 - 不允许重复 key;具体接口还会拒绝未知业务字段。
- 无参数或无数据时使用
{}。 - 金额使用普通十进制 JSON String,不使用 JSON Number 或科学计数法。
- 字段缺失、
null和空字符串具有不同语义。 - 业务 JSON 不要求字段排序,因为签名覆盖外层原始密文字符串。
5.2 发送方加密
发送方必须按以下顺序生成 payload 或 data:
- 将业务 JSON object 序列化为 UTF-8 字节。
- 使用密码学安全随机源生成 32 字节 AES 密钥。
- 使用密码学安全随机源生成 12 字节 IV。
- 使用 AES-256-GCM 加密业务 JSON,AAD 传空字节;将输出拆为密文和最后 16 字节 Tag。
- 使用接收方 RSA 公钥按
RSA-OAEP-256加密 AES 密钥。 - 对
encrypted_key、iv、cipher_text、tag分别进行标准 Base64 编码。 - 构造加密信封 JSON,并编码为 UTF-8 字节。
- 对完整信封字节进行标准 Base64 编码,得到外层
payload或data。
严禁复用 AES 密钥或 IV,严禁使用 timestamp、业务单号、UUID、密码或固定字符串生成 AES 密钥或 IV。
5.3 接收方解密
接收方必须按以下顺序解密:
- 严格 Base64 解码外层
payload或data。 - 按 UTF-8 解析加密信封 JSON,拒绝缺失、重复或未知字段。
- 校验
alg=RSA-OAEP-256、enc=A256GCM。 - 严格 Base64 解码四个二进制字段。
- 校验 RSA 密文为 384 字节、IV 为 12 字节、Tag 为 16 字节。
- 使用接收方 RSA 私钥解密
encrypted_key,结果必须是 32 字节 AES 密钥。 - 使用 AES-256-GCM、空 AAD 解密
cipher_text + tag;Tag 校验失败必须终止处理。 - 按 UTF-8 解析业务 JSON,校验顶层为 object 后进入业务参数校验。
任何解码、算法、长度、RSA-OAEP、GCM Tag 或 JSON 校验失败,都必须按解密失败处理,不得返回部分明文,也不得继续业务逻辑。
6. 数字签名
通用规则:
- 签名原文使用 UTF-8 编码。
- 字段之间只插入单个 LF 字符
\n(字节0x0A),不使用 CRLF。 - 原文结尾没有额外换行。
- 签名使用发送方 RSA 私钥执行
RS256(JavaSHA256withRSA)。 - 签名结果使用标准 Base64 编码。
- 验签必须使用发送方公钥,并使用收到的原始字段值构造原文。
6.1 请求签名原文
text
api_path + "\n" + platform + "\n" + timestamp + "\n" + payload示例:
text
/openapi/v1/users/register
partner_app
1787803200000
BASE64_ENCRYPTED_ENVELOPE6.2 响应签名原文
text
api_path + "\n" + platform + "\n" + timestamp + "\n" + code + "\n" + msg + "\n" + datatimestamp 和 code 使用 JSON Integer 的无前导零十进制形式;禁止小数、指数形式和前导 +。
7. 被调用方校验顺序
HoldFi 与第三方提供的服务端接口均按以下顺序处理请求:
- 校验 HTTP 方法、Content-Type 和请求体大小。
- 严格解析外层 JSON,并校验字段、类型、长度、
platform和timestamp格式。 - 根据
platform获取应用配置、调用方公钥和本方私钥,并检查应用状态。 - 使用请求来源 IP 校验当前应用的白名单。
- 校验时间窗口。
- 使用调用方公钥验证请求签名。
- 验签成功后才解析信封并执行 RSA-OAEP 与 AES-GCM 解密。
- 严格解析业务 JSON 并进入接口参数校验。
验签失败的请求不再进行后续解密。调用方处理响应时,也应先校验外层结构、platform 与时间窗口,再验签,最后解密 data。
8. 时间窗口、重放与重试
请求和响应相对接收方当前时间允许前后偏差 3 分钟:
text
abs(now_ms - timestamp) <= 180000双方服务器应保持可靠时间同步。
当前协议没有通用 nonce 或 request_id。不同接口的重复请求处理如下:
| 接口类型 | 重复处理 |
|---|---|
| 用户登记 | 按 platform + open_id 幂等 |
| 用户权益、账户信息、充值确认查询 | 只读,可安全重试 |
| 门户会话创建 | HoldFi 对完全相同的外层请求做 6 分钟重放拦截,重复请求返回 409002 |
网络超时后重试时,建议重新生成 AES 密钥、IV、timestamp、payload 和 signature。门户会话是否创建成功不明确时,不应依赖重复发送同一密文获取原结果;应生成新请求创建新会话,旧会话会在短期内自然过期。
9. 严格 JSON 与字段规则
- 外层报文、加密信封和业务对象均拒绝重复 key。
- 不执行字符串、整数、布尔值之间的自动类型转换。
- 各业务接口只允许文档列出的字段,未知字段返回
400001。 - 所有业务字段使用 snake_case。
- 时间统一使用 Unix 毫秒时间戳。
- 常量值使用全大写下划线形式。
公开业务码与含义见 常量与错误码。
10. 联调检查清单
- 双方确认环境、
platform、HTTP URL 和逻辑 API path。 - 确认双方公钥均为 RSA-3072 X.509 PEM,且与运行时私钥匹配。
- 先使用固定业务 JSON 验证加密、解密、签名和验签,再调用真实接口。
- 确认使用标准 Base64,不是 Base64URL。
- 显式确认 OAEP Hash 与 MGF1 Hash 都是 SHA-256。
- 确认签名原文使用 LF 且末尾无换行。
- 确认服务器时钟误差小于 3 分钟,出口 IP 已加入白名单。