签名与鉴权(RSA2)
平台采用非对称签名:商户用自己的私钥签名,平台用商户公钥验签——平台只保存商户公钥。回调方向相反:平台用平台私钥签名,商户用平台公钥验签(见 代收回调 / 代付回调)。
公共参数
每个请求必须携带以下公共参数(与业务参数一起参与签名):
| 参数 | 说明 |
|---|---|
merchant_no | 商户号 |
app_id | 应用 ID |
key_version | 商户公钥版本(支持平滑轮换) |
timestamp | Unix 秒,与服务器相差 ≤ 5 分钟 |
nonce | 随机串 ≥16 位,有效期内不可重复 |
sign_type | 固定 RSA2(SHA256withRSA,RSA-2048,PKCS#1 v1.5) |
sign | 签名值,Base64 标准编码、无换行 |
签名算法(四步)
- 取公共参数 + 业务参数,排除
sign本身,并排除值为null或空字符串的字段(sign_type也参与签名,防降级)。 - 按参数名 ASCII 升序排序。
- 对每个值做 RFC3986 percent-encode,再拼成
k1=v1&k2=v2&…(key 本身不编码),得到待签串message(UTF-8 字节)。 sign = Base64( RSA_SHA256_Sign(商户私钥, message) )。平台用你注册的公钥验签。
percent-encode 必须是 RFC3986
空格 → %20(不是 +);A-Za-z0-9-_.~ 不编码;十六进制大写;按 UTF-8 字节。 Java 不要用 URLEncoder.encode(表单编码,空格会变 +);PHP 用 rawurlencode;Go 不要直接用 url.QueryEscape。三端必须产出完全一致的 message。
参与签名的值只能是标量
字段值只能是字符串或整数,不得是 JSON 对象/数组;金额一律整数最小单位。attach 等透传字段必须是字符串(复杂内容请先自行 JSON 序列化成字符串)。
示例:
text
message = amount=10000&app_id=app_10001¤cy=PHP&key_version=1&mch_order_no=T20260528001
&merchant_no=M100001&nonce=a1b2c3d4e5f6a1b2&product_code=QRPH&sign_type=RSA2×tamp=1769990400
sign = Base64( SHA256withRSA( 商户私钥, message ) )密钥与编码格式
- 私钥:PKCS#8 PEM(
-----BEGIN PRIVATE KEY-----);商户自行生成 RSA-2048 密钥对,私钥绝不离开你的服务器。 - 公钥:X.509/SPKI PEM(
-----BEGIN PUBLIC KEY-----),注册给平台(绑定app_id+key_version)。 - 签名输出与公钥传输:Base64 标准编码(非 URL-safe)、无换行。
生成密钥:
bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out merchant_private.pem
openssl pkey -in merchant_private.pem -pubout -out merchant_public.pem安全校验顺序
请求按以下顺序校验,任一不过即拒:
- 限流(
1007) - 公共参数齐全
- 按
merchant_no/app_id/key_version取商户公钥 - 时间窗:与服务器相差 ≤ 5 分钟(
1003) - RSA 验签(
1002) - 商户 / App / 公钥状态正常(
1006) - 来源 IP 在你配置的白名单内,白名单为空 = 拒绝(
1005) - 防重放:验签通过后才消费
(app_id, nonce)(1004)