对于 API 文档中要求使用新的验签方式的接口请求,以下是验签的详细说明及注意事项:
1. 首先必须在 Ping++ 管理平台上[配置商户公钥](https://help.pingxx.com/article/123161)。
2. RSA-SHA256 签名请使用上述生成的商户私钥,以及请求体 + 请求 URI + 当前时间的 Unix 时间戳生成。
3. 在请求头中添加以下字段
Authorization 字段,其值为 Bearer + API key,例如:Bearer sk_live_*******GujD
Pingplusplus-Signature 字段,其值为示例代码中的 $signature
Pingplusplus-Request-Timestamp 字段,其值为示例代码中的 $request_time
Content-Type 字段,值为 application/json
以下为各个语言签名生成的示例:
PHP :
$request_time = time();//请求时间 $request_body = '<请求内容>'; $request_uri = '/v1/batch_transfers'; $data_to_be_signed = $request_body . $request_uri . $request_time; // 拼接带签名字符串 $private_key = '<商户私钥>'; openssl_sign($data_to_be_signed, $signature, $private_key, 'sha256'); // $private_key 为商户私钥 $signature = base64_encode($signature);// 生成的签名
Node.js:
var requestTime = Date.parse(new Date()) / 1000; // 请求时间 var reuestBody = '<请求内容>'; var privateKey = '<商户私钥>'; var requestUri = '/v1/batch_transfers'; // 请求 URI(包括 URL 参数) var data = reuestBody + requestUri + requestTime; // 拼接带签名字符串 var sign = crypto.createSign('RSA-SHA256').update(data, "utf8"); var signature = sign.sign(privateKey, 'base64'); // 生成的签名
Ruby:
request_time = Time.now.to_i.to_s # 请求时间 request_body = '<请求内容>' request_uri = '/v1/batch_transfers' # 请求 URI(包括 URL 参数) data = request_body + request_uri + request_time; # 拼接带签名字符串 pkey = OpenSSL::PKey.read('<商户私钥>') # 商户私钥 signature = Base64.strict_encode64(pkey.sign(OpenSSL::Digest::SHA256.new, data)) # 生成的签名
Go:
func GenSign(data []byte, privateKey []byte) (sign []byte, err error) { block, _ := pem.Decode(privateKey) if block == nil { return nil, errors.New("private key error") } priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } hashFunc := crypto.SHA256 h := hashFunc.New() h.Write(data) hashed := h.Sum(nil) return rsa.SignPKCS1v15(rand.Reader, priv, hashFunc, hashed) }
Python:
# -*- coding: utf-8 -*- import time import json from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from base64 import b64encode private_key = open('<商户私钥>', "r").read() request_body = {'<请求内容>'} if request_body: verify_data = [json.dumps(request_body).encode("utf-8")] else: verify_data = [] build_url = '/v1/batch_transfers' # 请求 URI(包括 URL 参数) timestamp = int(time.time()) verify_data.extend([build_url, repr(timestamp)]) rsa_data = "".join(verify_data) rsa_key = RSA.importKey(private_key) signer = PKCS1_v1_5.new(rsa_key) digest = SHA256.new(rsa_data) sign = signer.sign(digest) signature = b64encode(sign)
C#:
string body = "<请求内容>", sign = "", data = ""; string timestamp = ((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000).ToString();//请求时间 body = JsonConvert.SerializeObject(param, Formatting.Indented); string requestUri = "/v1/batch_transfers"; //请求 URI(包括 URL 参数) data = body + requestUri + timestamp; byte[] dataBytes = Encoding.UTF8.GetBytes(data); var privateKeyFile = new FileStream("<商户私钥>", FileMode.Open); string privateKeyContent = (new StreamReader(privateKeyFile)).ReadToEnd(); privateKeyFile.Close(); privateKeyContent = privateKeyContent.Replace("\r", "").Replace("\n", ""); if (privateKeyContent.StartsWith("-----BEGIN RSA PRIVATE KEY-----")) { privateKeyContent = privateKeyContent.Substring(31); } if (privateKeyContent.EndsWith("-----END RSA PRIVATE KEY-----")) { privateKeyContent = privateKeyContent.Substring(0, privateKeyContent.Length - 29); } var privateKeyData = Convert.FromBase64String(privateKeyContent); if (privateKeyData.Length < 162) { throw new PingppException("Private key content is incorrect."); } //解析privatekey var mem = new MemoryStream(privateKeyData); var binr = new BinaryReader(mem); try { var twobytes = binr.ReadUInt16(); switch (twobytes) { case 0x8130: binr.ReadByte(); break; case 0x8230: binr.ReadInt16(); break; default: return null; } twobytes = binr.ReadUInt16(); if (twobytes != 0x0102) return null; var bt = binr.ReadByte(); if (bt != 0x00) return null; var elems = GetIntegerSize(binr); var modulus = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var e = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var d = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var p = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var q = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var dp = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var dq = binr.ReadBytes(elems); elems = GetIntegerSize(binr); var iq = binr.ReadBytes(elems); var rsa = new RSACryptoServiceProvider(); var rsaParam = new RSAParameters { Modulus = modulus, Exponent = e, D = d, P = p, Q = q, DP = dp, DQ = dq, InverseQ = iq }; rsa.ImportParameters(rsaParam); } sign = Convert.ToBase64String(rsa.SignData(dataBytes, "SHA256"));
Java:
import org.apache.commons.codec.binary.Base64; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; String requestTime = Integer.valueOf((int) (System.currentTimeMillis() / 1000)).toString(); // 请求时间 String requestBody = "<请求内容>"; String requestUri = "/v1/batch_transfers"; // 请求 URI(包括 URL 参数) String data = requestBody + requestUri + requestTime; // 拼接待签名字符串 String PEMEncodedPrivateKey = "<商户 PKCS8 私钥>"; PEMEncodedPrivateKey = PEMEncodedPrivateKey.replaceAll("(-+BEGIN (RSA )?PRIVATE KEY-+\\r?\\n|-+END (RSA )?PRIVATE KEY-+\\r?\\n?)", ""); byte[] privateKeyBytes = Base64.decodeBase64(PEMEncodedPrivateKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privateKey = kf.generatePrivate(keySpec); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(data.getBytes("UTF-8")); byte[] signBytes = signature.sign(); String signatureString = Base64.encodeBase64String(signBytes).replaceAll("\n|\r", ""); // 生成的签名
上一篇 微信公众号报错:对支付渠道的请求未能成功。来自 wx_pub 渠道的错误信息:商户号mch_id与appid不匹配怎么办?