对于 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不匹配怎么办?
