XD-OP-12 · DOC

凭证与密钥管理(生成 / 轮换 / 停用 / 应急吊销)

私钥只在自己的服务器生成与保存;`kid` 在上传公钥时由平台派生。本篇给出生成、轮换、停用与应急吊销的完整顺序。

小豆支付开放平台是面向已有自有系统的商家的支付接入服务:通过服务器对服务器的接口,把支付宝与微信收款接进商家自己的系统,并以签名事件通知回传支付状态;资金由持牌支付公司收付结算。个人也能申请:主体类型选「个人」,身份证 + 本人银行卡即可完成商户进件。央行持牌机构清算,安全可靠的个人和企业支付接口。

商家最关心的问题是「怎么换钥匙不中断收款」。本篇按实际操作顺序写清楚,包括切换瞬间的真实行为。

1. 你手上的凭证有哪些

凭证用途存放位置
appId应用标识(随六头发送)你的服务端配置
kid(密钥标识)标明本次签名用的是哪把钥匙你的服务端配置
RSA2 密钥对请求签名/验签公钥在门户、私钥只在你本地
IP 白名单 + 来源 IP 校验开关请求来源判定(校验可自助开启/关闭)门户维护
notifyUrl事件投递地址应用配置
returnUrl收银完成后的回跳地址(下单时冻结)下单请求

1.1 kid 长什么样、从哪来(集成方必读)

  • 格式:mkid_ + 16 位小写十六进制(共 21 字符),例如 mkid_5f3c1a7b52e84d06。
  • 来源:kid 不是平台预先发放的——在你上传公钥成功的那一刻,平台按公钥指纹派生并回显(第 2 节第 5 步)。没有上传过公钥的应用,任何地方都不存在它的 kid;对接面板里留一个「密钥标识」输入框、等你去"找"一个填,本身就是找错了方向:先生成密钥对并在门户上传公钥,kid 随即出现在门户「应用凭证」页。
  • 本地派生(可自动计算,无需向平台索取):kid 是公钥的确定性函数,工具链可以本地算出同一个值——
  1. 从你的密钥对导出公钥 SPKI PEM(-----BEGIN PUBLIC KEY----- 头,RSA 2048/4096);
  2. 规范化:CRLF→LF、去掉首尾空白(末尾不留换行);
  3. kid = "mkid_" + SHA-256(第 2 步文本的 UTF-8 字节) 的前 16 位小写十六进制。

只要上传到门户的 PEM 与本地计算用的是同一份文本(即「导出 → 规范化 → 上传」同一路径产出),本地算出的 kid 与平台回显必然一致。

  • 两个 kid 别混:门户「应用凭证」页同时展示两个——
  • 「商户公钥 kid」:你自己的,签名请求头 X-XD-Key-Id 填的就是它;
  • 「平台公钥 kid」:平台的签名密钥标识(用于你核验平台响应/事件签名),不要填进请求头,也不要填进对接配置的「密钥标识」项。

2. 生成密钥对

  1. 在门户「应用凭证」页发起密钥生成。密钥对在你的浏览器里本地生成,私钥从生成到保存都不经过平台。
  2. 私钥保存到你自己的受控存储(密钥管理服务或受权限保护的凭证文件),权限最小化。
  3. 上传公钥时,平台会要求一次持有证明:用新私钥对一段确定性挑战串(与你的应用标识与公钥指纹绑定)签名。这证明你确实持有对应的私钥,而不是只复制了一个公钥。挑战串由门户当场给出、直接复制使用,不要自行拼装(同一应用 + 同一公钥永远得到同一串,所以「先算好再签」是可复现的)。
  4. 提交后需通过操作二次验证(发到你绑定手机的操作码)——这是为了防止会话被盗后被人悄悄换钥匙。
  5. 成功后平台回显公钥指纹与 kid;请把 kid 记到你的配置里。

挑战串是确定性的:同样的应用与公钥指纹永远得到同一段挑战串。所以「先算好再签」是可复现的——这也是你本地自检的办法。

3. 轮换密钥(换钥匙)

切换语义(必须准确理解,直接决定你会不会掉单):

  • 轮换是原子的:提交成功后,新 kid 立即生效,你的请求必须立刻改用新私钥签名。
  • 默认态下旧密钥在同一瞬间失效:平台只接受当前生效的 kid 与公钥。历史 kid 在门户「密钥历史」里保留记录,用于审计与追溯,默认不能继续用于签名。

⚠️ 门户的历史条目只显示「已轮换」这一事实,不代表宽限窗已开启——宽限窗只能由平台运营开启,且门户目前不展示窗口状态与到期时间(如需确认,请与对接人核对)。看到历史条目就去删旧配置的前提是「没开宽限窗」,不要反过来假设。

  • 因此轮换会有一个从平台切换完成到你服务端改配置之间的短窗口,期间用旧私钥签名的请求会得到 401。
  • 可选的过渡宽限窗:如果这个短窗口对你不可接受,可在轮换之前联系平台运营申请开启「旧密钥验签宽限窗」——
  • 窗口内,你已轮换下线的旧 kid 仍可通过签名验证;窗口到期自动失效,旧密钥立即恢复拒绝;
  • 宽限窗只允许验签:它不恢复任何管理/配置权限,也不改变应用状态;
  • 时长由平台按固定上限核定(不超过 72 小时),平台可随时立即关闭;窗口到期或关闭后旧 kid 立即恢复拒绝,且不会再有任何提示;
  • 密钥疑似泄露时不适用:那种情况下先轮换止血,不要用宽限窗延长旧钥匙的寿命。

推荐操作顺序(默认态:把窗口压到最小):

若已与平台约定开启宽限窗,第 1 步改为「先确认宽限窗已开启、记下到期时间」,第 4 步的 401 重试可以放宽;其余不变。

  1. 选低峰期,准备好一键切换配置(新私钥 + 新 kid)。
  2. 在门户完成轮换(生成 → 持有证明 → 二次验证 → 提交)。
  3. 提交成功后立即切换你服务端的配置并发布(默认态下旧私钥从提交那一刻起就签不通了,切换越慢掉单越多)。
  4. 切换期间对 401 做有限次重试(不要无限重试);确认新配置生效后,观察下单成功率与签名相关告警恢复。
  5. 确认无异常后,销毁本地旧私钥(避免误用)。
  6. 把本次轮换的时间、kid、执行人、结果记进你的变更记录(后续对账时会用到)。

先在预发环境演练一遍上面的顺序(用同一套流程、同一份配置项),再对生产执行。

4. 停用与吊销

动作谁触发效果
停用应用(暂停)平台侧(含商家申请)商家 API 全部不可用:状态非 ACTIVE 时密钥解析在鉴权阶段就中性失败,所以你拿到的是 401 而不是 403,存量订单的查询/关单/退款同样打不通,善后走平台人工通道
吊销应用平台侧(双人复核)永久终态:表现同上(401),退款仅保留平台人工通道
冻结(风控)平台侧风控新增下单/支付尝试被拒(403);存量查询/关单/退款不受影响
移除 IP 白名单条目门户对应出口的请求立即 403
关闭来源 IP 校验门户(自助,短信二次验证)所有来源立即不再被校验;白名单条目原样保留,随时可一键重新开启。平台边缘的来源判定不受本开关影响

停用/吊销期间没有"只读自留地":这两种状态下应用不再 ACTIVE,鉴权阶段就中性拒绝(不区分应用不存在与状态异常,是防枚举的有意设计),因此存量订单也查不到。需要善后时按 错误码与排障 联系平台走人工通道。

吊销后不再产生任何事件:该应用的事件不会落库,事后也无法补发。所以吊销前先把在途订单的终态用查单补齐。

5. 私钥丢失或怀疑泄露(应急)

按这个顺序做,别先到处找原因:

  1. 立即在门户轮换密钥(见第 3 节)——不需要停机、不需要等平台人工介入。
  2. 检查是否有人用旧密钥发起过请求:看你自己的调用日志与平台的 401/403 告警;有异常调用请一并反馈。
  3. 若怀疑 appId 也已被滥用(例如出现在不该出现的地方),走官网反馈渠道申请停用应用并说明情况。
  4. 轮换完成后,销毁本地旧私钥与任何携带它的临时文件/镜像层/构建缓存。
  5. 顺手确认来源 IP 校验处于开启(门户「IP 白名单」页)——它在关闭状态下不会拦住任何人,而泄露场景下它是密钥之外的唯一一道门槛。

6. 多环境与多应用纪律

  • 一应用一套密钥;不要用同一套密钥跨应用或跨环境(测试/生产必须分开)。
  • 不要复制已有应用的密钥去创建新应用——应当为新应用重新生成。
  • 密钥配置项不进代码仓库:用运行时注入或密钥服务;本地开发也不要用生产密钥。

7. 常见误区

  1. ❌「轮换后新旧钥匙能默认同时用一段时间」——默认不能,切换是原子的,旧密钥立即失效;唯一例外是平台事先显式开启的限时宽限窗(见第 3 节),它不是默认行为,也不由商家自助开启。
  2. ❌ 在轮换前就把服务端切到新私钥——此时平台还没切,你会一直 401(顺序是:平台切完 → 你立刻切)。
  3. ❌ 把私钥放进前端或移动端用于「让客户端直连」——私钥永远只在服务端。
  4. ❌ 用同一个 kid 传不同的私钥(例如两台服务器各生成一套)——只有与门户登记公钥匹配的那一份能通过验签。
  5. ❌ 密钥泄露后先排查后轮换——先轮换(止血),再排查。
  6. ❌ 到处找不到「以 kid_ 开头的密钥标识」——真实格式是 mkid_…(21 字符),且它在上传公钥那一刻才出现:没上传过公钥的应用本来就没有 kid。顺序永远是「生成密钥对 → 门户上传公钥 → 复制回显的 kid(或按第 1.1 节本地派生)」。
内容源:open-platform/docs/12-credential-key-management.md · 最后更新 2026-10-04 下载本文 .md 在 GitHub 查看