Skip to content

签名与鉴权(RSA2)

平台采用非对称签名:商户用自己的私钥签名,平台用商户公钥验签——平台只保存商户公钥。回调方向相反:平台用平台私钥签名,商户用平台公钥验签(见 代收回调 / 代付回调)。

公共参数

每个请求必须携带以下公共参数(与业务参数一起参与签名):

参数说明
merchant_no商户号
app_id应用 ID
key_version商户公钥版本(支持平滑轮换)
timestampUnix 秒,与服务器相差 ≤ 5 分钟
nonce随机串 ≥16 位,有效期内不可重复
sign_type固定 RSA2(SHA256withRSA,RSA-2048,PKCS#1 v1.5)
sign签名值,Base64 标准编码、无换行

签名算法(四步)

  1. 公共参数 + 业务参数,排除 sign 本身,并排除值为 null 或空字符串的字段(sign_type 也参与签名,防降级)。
  2. 按参数名 ASCII 升序排序。
  3. 对每个值做 RFC3986 percent-encode,再拼成 k1=v1&k2=v2&…(key 本身不编码),得到待签串 message(UTF-8 字节)。
  4. sign = Base64( RSA_SHA256_Sign(商户私钥, message) )。平台用你注册的公钥验签。

percent-encode 必须是 RFC3986

空格 → %20(不是 +);A-Za-z0-9-_.~ 不编码;十六进制大写;按 UTF-8 字节。 Java 不要用 URLEncoder.encode(表单编码,空格会变 +);PHPrawurlencode;Go 不要直接用 url.QueryEscape。三端必须产出完全一致的 message。

参与签名的值只能是标量

字段值只能是字符串或整数,不得是 JSON 对象/数组;金额一律整数最小单位。attach 等透传字段必须是字符串(复杂内容请先自行 JSON 序列化成字符串)。

示例:

text
message = amount=10000&app_id=app_10001&currency=PHP&key_version=1&mch_order_no=T20260528001
         &merchant_no=M100001&nonce=a1b2c3d4e5f6a1b2&product_code=QRPH&sign_type=RSA2&timestamp=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

安全校验顺序

请求按以下顺序校验,任一不过即拒:

  1. 限流(1007)
  2. 公共参数齐全
  3. merchant_no / app_id / key_version 取商户公钥
  4. 时间窗:与服务器相差 ≤ 5 分钟(1003)
  5. RSA 验签(1002)
  6. 商户 / App / 公钥状态正常(1006)
  7. 来源 IP 在你配置的白名单内,白名单为空 = 拒绝(1005)
  8. 防重放:验签通过后才消费 (app_id, nonce)(1004)