商户 API 密钥这东西,平时没人想碰它,但真到该换的时候,很多人会发现自己不知道从哪下手——因为密钥一改,所有正在跑的支付请求、回调验签,全都会在同一秒失效。
直接改密钥,是最省事也最危险的做法。改完那一瞬间,线上正在进行的支付会报"签名错误",回调会验签失败,用户看到的可能就是"支付失败"或者"订单状态异常"。业务量越大,这一刀切下去越疼。
这篇文章讲的就是怎么安全地换:不停机、不中断业务,把商户 API 密钥平稳地轮换掉。这套流程我自己在线上跑过不止一次,最长的一次只用了 40 分钟,全程零掉单。步骤是完整的、可落地的,照着做就行。
先把概念说清楚:这里说的"密钥"是哪一个
易支付体系里,跟商户 API 打交道的主要是两样东西:商户号(pid)和商户密钥(key)。pid 用来标识你是谁,key 用来做签名——你发起的每一次请求,都要用 key 对参数做一次签名,服务端用你留存的 key 做同样的签名来比对,一致才认这笔请求是你发的。
所以 key 是整个 API 安全的核心。key 一旦泄露,别人就能冒充你的商户去下单、查单、甚至篡改回调。这也是为什么 key 需要定期轮换——不是坏了才换,而是为了控制"泄露后可能造成的损失窗口"。
本文要轮换的,就是这个 key。pid 一般不需要动,除非你要换主体。
为什么"直接改"会翻车
理解了 key 的作用,你就明白为什么直接改会出问题:key 是"请求方"和"验证方"共同持有的秘密,两边必须同时一致,签名才验得过。
你改了服务端(易支付后台)的 key,但你的业务系统(网站、App、脚本)里还在用旧 key,那么业务系统发出去的签名全是旧 key 签的,服务端用新 key 验签,必然失败。反过来也一样——你改了业务端,服务端还没改,同样失败。
这就是"密钥轮换"和"改个密码"的本质区别:改密码是单边的,改完即生效;密钥是双边的,任何一边先改,中间都有一段"对不上"的空窗期。
而"不停机"的难点,恰恰就卡在这个空窗期上。要做到不停机,就得想办法让新旧两个 key 在一段时间内同时有效,让两边能平滑地、无感知地完成切换。
核心思路:双密钥过渡,而不是单点切换
不停机轮换密钥,业界通用的做法是双密钥过渡,核心就一句话:永远让"旧 key 还能用"的状态保持到最后一刻。
具体逻辑是这样的(以一次真实的轮换为例,线上业务量约 日均 2000 笔):
- 先给服务端配置"新旧两个 key 都认可"的能力(或者利用平台本身的密钥切换机制)。
- 业务端先继续用旧 key,此时一切照旧,不受影响。
- 把业务端逐步切换到新 key,切换过程中,还没切到的请求用旧 key,已经切到的用新 key,两个 key 服务端都认,所以都不会失败。
- 等业务端全部切完、观察一段时间确认稳定后,再关掉旧 key。
这个过程的精髓是"先并存,再切换,最后收尾"。只要"并存"这一步做对了,后面就只是一个时间问题,不用抢时间、不用半夜停机、更不用赌运气。
完整步骤:七步走
下面是把上面思路落地的完整步骤。假设你的业务系统已经通过易支付 API 正常收款,现在要轮换 key。
第一步:备份现状,先留退路
动手之前,先把当前的信息完整备份一遍:现在的 pid、旧 key、所有用到 key 的配置文件、代码位置、以及当前的回调地址。别小看这一步,轮换密钥最容易出的问题,就是"改到一半发现漏了某个地方没改,又忘了原来的值"。把旧 key 抄下来存好,万一要回滚,你手里有牌。
第二步:盘点所有用到 key 的地方
这一步是很多人翻车的根源——你以为只有一个地方用了 key,其实有七八个。我上一次轮换时,光盘点就盘出 9 处用到 key 的地方:主站下单代码、回调验签代码、后台管理脚本、对账脚本、2 个定时任务、测试环境……一个都不能漏。
漏掉任何一个,等你把旧 key 关掉的那一刻,那个漏掉的地方就会突然失效,而且往往是在半夜、在你最没防备的时候。
第三步:服务端开启双密钥并存
这一步是"不停机"的关键。去易支付后台,看它是否支持"密钥轮换"或"双密钥"机制;如果支持,就按它的流程生成新 key,让新旧两个 key 同时处于有效状态。
如果平台不支持双密钥(有些老版本或第三方平台没有这个能力),那就要换个思路:先把新 key 生成好,但别急着停掉旧 key 的调用方,通过"业务端灰度切换"来过渡(下面第五步会讲)。
不管平台支不支持,核心目标都一样:确保在业务端切换完成之前,旧 key 始终有效。
第四步:业务端支持"新旧双 key"读取
这一步是工程上的准备。把业务系统里读取 key 的地方,改造成能同时读"旧 key"和"新 key"两份配置。下单请求先用新 key 签名,如果失败(比如新 key 还没完全生效),自动回退用旧 key 重试。
这个"新优先、旧兜底"的机制,是平滑切换的核心。有了它,你切换 key 就不需要"赌一个精确的时间点",而是一个可以慢慢来的过程。关于签名和 key 在请求里到底怎么起作用的细节,如果你对签名机制还不熟,可以先看支付回调sign签名:MD5和RSA到底有什么区别、该用哪个,把签名原理吃透,这里就不会觉得抽象。
第五步:灰度切换,别一把梭
如果你有多个环境(测试、预发、生产),或者多个业务模块,不要一次性全切。先切测试环境,验证新 key 能正常下单、回调验签通过;再切预发;最后才切生产。生产环境里,如果模块多,也可以按模块逐个切。
灰度的意义在于:万一新 key 哪里配错了,你只影响了一小块,而不是全站瘫痪。一次全切的风险,是"要么全对、要么全错",而全错的代价你承受不起。
第六步:观察验证,确认稳定
切完之后,别急着宣布完成。至少观察一个完整的业务周期(建议 24 到 48 小时),重点盯这几件事:
- 下单成功率有没有异常下降。
- 回调验签有没有失败的记录。
- 对账能不能对上,有没有"掉单"。
- 日志里有没有签名相关的报错。
这一步最容易犯的错,是"切完看一眼没报错就收工"。密钥问题很多是延迟暴露的——比如某个低频的定时任务,可能几天才跑一次,它用的还是旧 key,等你关掉旧 key 后它才第一次失败。所以观察期要够长,覆盖到那些低频场景。
第七步:确认稳定后,关掉旧 key
等观察期结束、确认所有地方都已经切到新 key 且运行稳定,最后一步才是在服务端关掉旧 key,让旧 key 彻底失效。
这一步是"收尾",也是整个轮换流程的最后一道。旧 key 不关,就意味着那个已经"退役"的密钥还一直有效,泄露风险窗口就一直存在。轮换的意义,就在于最后把旧 key 彻底作废。
一个最容易踩的坑:key 里的换行符
这里专门说一个坑,因为它太常见了,而且它恰好会在"轮换密钥"这个场景里集中爆发。
很多人生成新 key 之后,复制粘贴到配置文件里,结果 key 的末尾多了一个换行符、或者开头多了一个空格。这个看不见的字符,会让签名结果和预期完全对不上,于是新 key 一上线就报"签名错误"。
更麻烦的是,因为问题出在"不可见字符"上,你盯着 key 看半天也看不出哪里错了。我之前专门写过一篇易支付支付失败排查实录:藏在密钥里的换行符,就是讲这个坑的排查过程。轮换密钥时,新 key 一配上去就先做一次"签名自测",用新 key 手动签一次、和文档给的示例值对一下,能第一时间把这个坑排掉。
轮换之后,别忘了这几件事
密钥轮换不是改完 key 就结束了,还有几件收尾的事要做:
- 清理旧 key 的所有残留:配置文件、环境变量、数据库、代码注释里的旧 key,全部清干净,别留着。
- 检查是否有硬编码:有些开发者图省事,把 key 直接写死在代码里。轮换时最容易漏的就是这种硬编码,因为它在代码里,不在配置里。
- 更新文档和交接说明:如果你有团队,把新的 key 管理方式、轮换流程更新到文档里,避免下次轮换又从头摸索一遍。
- 建立轮换周期:密钥不是换一次就一劳永逸,建议定一个周期(比如每季度或每半年)定期轮换,把"泄露窗口"控制在可接受的范围内。
密钥轮换的本质,是把"一次性风险"拆成"可控过程"
很多人怕换密钥,是因为他们把换密钥想象成"一刀切"——改一下,要么成、要么崩。但真正专业的做法,是把它拆成一个可以慢慢来、可以随时回退、每一步都看得见结果的渐进过程。
双密钥并存,是"不停机"的技术前提;灰度切换,是"不翻车"的操作前提;观察验证,是"不埋雷"的收尾前提。这三件事做到位,密钥轮换就不再是一件让人提心吊胆的事,而是一个routine——一个你按部就班、半小时就能稳妥完成的常规操作。
安全这件事,从来不是靠"赌一把"完成的,而是靠"把每一步都踏稳"完成的。密钥轮换,尤其如此。