签名验签
更新时间:2025.01.02●V2接口和V3接口的签名方式一样吗? | |
不一样。 |
●为什么微信支付的回调缺少签名的几个HTTP头? | |
微信支付的回调,在HTTP头部包含了以下四个HTTP头: |
●Http头缺少Accept或User-Agent | |
该报错是因为基础规则设置错误导致,Accept和User-Agent都必须设置,缺一不可。 |
●如何在程序中加载私钥? | |
推荐使用微信支付提供的SDK。你也可以查看下列编程语言的示例代码。 JAVA 1/** 2 * 获取私钥。 3 * 4 * @param filename 私钥文件路径 (required) 5 * @return 私钥对象 6 */ 7public static PrivateKey getPrivateKey(String filename) throws IOException { 8 9 String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8"); 10 try { 11 String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") 12 .replace("-----END PRIVATE KEY-----", "") 13 .replaceAll("\\s+", ""); 14 15 KeyFactory kf = KeyFactory.getInstance("RSA"); 16 return kf.generatePrivate( 17 new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); 18 } catch (NoSuchAlgorithmException e) { 19 throw new RuntimeException("当前Java环境不支持RSA", e); 20 } catch (InvalidKeySpecException e) { 21 throw new RuntimeException("无效的密钥格式"); 22 } 23} 24 PHP 1/** 2* Read private key from file 3* 4* @param string $filepath PEM encoded private key file path 5* 6* @return resource|bool Private key resource identifier on success, or FALSE on error 7*/ 8public static function getPrivateKey($filepath) { 9 return openssl_get_privatekey(file_get_contents($filepath)); 10} 11 .NET 1protected string sign(string message) 2{ 3 // 需去除私钥文件中的-----BEGIN/END PRIVATE KEY----- 4 string privateKey = "MIIEvgIBADANBgkqhkiG...30HBe+GD1tntZgf6I1Y0ZpHZ"; 5 byte[] keyData = Convert.FromBase64String(privateKey); 6 using (CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob)) 7 using (RSACng rsa = new RSACng(cngKey)) 8 { 9 byte[] data = System.Text.Encoding.UTF8.GetBytes(message); 10 return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); 11 } 12} 13 GO 1package main 2import ( 3 "crypto/x509" 4 "encoding/pem" 5 "fmt" 6 "log" 7) 8 9func main() { 10 var pemData = []byte(` 11-----BEGIN PRIVATE KEY----- 12MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1yhh6LNB8nXmOSxdGKWmDh0OxAM/wnGyHKSD9tcEhMQTe+wabce0POXzejCmwFBzZa7ZmxH5LoAeyT7Fpwb7pptbbDx58CxCYhNEdQ2XrFILUCq3daMj++KQlyDp8U0NspFKsO57gSlihAJ49DzcXQb7Vs5daIvtLapIouPyixAE5uDL+afmJ+bXC11xP5sPWw1RfXynW3vbEyfRol9hQyQWfmO15GSZi6TTAhTKaW31yKaQNChy06K+LsE9JAU+ESxihthtGiMbY3fFRyhF9Ka2e0wIOz6UdcfwMjxXWRV4OLD1uFG9IYbUiugmYtDyIYZaFDPYdi/+Rjm10Ps5lAgMBAAECggEAb19kRZ2lEWOM8D9S//opGZrKPuvneVrsJpZtDuLGcqZMfKvALYXLnZMzzEiE1cpMrmuOMUHaukxNytGGOOupIg7D/SszGv3QahCc6Ne83hwP1wa/5DDpS0RblIYqRrbgTPQTbk+Mk48Y43K0f2YN82KlHtnLNT7PRDIDX42Nwc1X8f4JcfyKUE/pOSn+YUlu5Edu6QYbWJWS7mlojEZ/wuWbSymbs6mVVkKeSWGTIh1v4n2F3Gj6ckUDlt4aZWTVcBa2+ZvSE2h5frSH0snpdGV1bW44IqE3NkwfTQ7JI34CVJdhb3goIyoTmiz6NGEZuiyr8gP9IOjqPfeP7GO5YQKBgQDuB1CT8ksO4SqR3skRkdCQW7kOogZgDThei+3HUMOsHr8L42oYkJDmk2res1ow/mz6SoIV4w6mvvUSnACxdtYA1AzUEs3jvltv8cQ1HAuDhLRslWrhSoxrQQh20yrVxxGN0J4DdCAGURSUwypzUHR+mlfcjacPyxKUsT41+8zG+QKBgQDDg8ZGivuV794RuA3cfpitUFG+0nA0ZS3qAZqlA3ufnCudHQixFIsf83Q7sX7pBob5PNONqsbv0OKpC3/xJRSPIwjWTBUPlDLrsGajKMhUPtkWo4zkfrSa8XaUpUVDU0qTzS71f9Aab3SkPH1d1o4cQxO08axGLbmTV/46QCBzQKBgDd7ZQDXPT+epHmT4HJD9sVvW9dZVPsWmckP/MC0xqdcE1QGEjjfmablPcfjLma1J1m//Ep1vniHkkBgNJkpBgDzbHoSWAN5335ccEug2d4yFIwq19rjsY9efUaVOirSV/kiY3KSotRWGeIDC+YNHtpTx58VNZes0gvutH2Iz9ahAoGAUcoWb/xEMv0dURxF8C+lfxtSlxlBhymsg3AYWV+Tn7mdJSS4Nhv592vI/A/Mn37zh+BCP8lpX3lq2HzPEPoKF7b4Q22ggdvlSQT6SMT8mTtfbyPSyRAQdWZQZnyVkTD3TvPDg7CKD1As8KFiFuXPAD2KgI9nVz6XhNBpjZ8rbyECgYEAsOrm1hbNZbvlNhnuUjw5DTgTuJ3B0j1aK/7C2EQWR+mIG2q5TKDC6xNdszV0gK1/TbJk4RNgQo0JLkuZ2Xk2Q8KhaNe+X8SYP9CFKIsXuhGrYI5ICjipov5oJqjESV4wle575eWwdPgF1ICabpIqdnX2MxS9tkk830uXxPrXpRA= 13-----END PRIVATE KEY-----`) 14 15 block, rest := pem.Decode(pemData) 16 if block == nil || block.Type != "PRIVATE KEY" { 17 log.Fatal("failed to decode PEM block containing public key") 18 } 19 20 pri, err := x509.ParsePKCS8PrivateKey(block.Bytes) 21 if err != nil { 22 log.Fatal(err) 23 } 24 25 fmt.Printf("Got a %T, with remaining data: %q", pri, rest) 26} 27
|
●为什么请求返回401 Unauthorized? | ||||||||||||||||||||||||||||||||||
请根据报文中的message信息,在下表中找到错误的原因和对应的解决方案。
|
●如何定位“错误的签名,导致验签失败”的错误? | |||||||
为了方便开发者定位,我们对于验签失败,会在应答的错误详情detail中加入验签信息。验签信息是我们根据商户的HTTP请求构造签名串的各种信息。
1{ 2"code": "SIGN_ERROR", 3"message": "错误的签名,验签失败", 4"detail": { 5 "field": "signature", 6 "issue": "sign not match", 7 "location": "authorization", 8 "sign_information": { 9 "method": "GET", 10 "url": "/payscore/user-service-state?service_id=500001&appid=wxeaf7bf1de621b0c2&openid=oWm9Z5JQwgV7BKAQUeKsUMVSjTpQ", 11 "truncated_sign_message": "GET\n/payscore/user-service-state?service_id=500001&appid=wxeaf7bf1de621b0c2&openid=oWm9Z5JQwgV7BKAQUeKsUMVSjTpQ\n1559194069\n18a427e78d2344e1a71156a2690cc4d6\n\n", 12 "sign_message_length": 157 13 } 14} 15} 16 建议开发者在程序中将自己组装的签名串以及签名串的字节长度在调试信息中输出,跟微信支付返回的验签信息进行仔细对比,排查以下几种常见的错误: 签名串的最后一行没有附加换行符 如果请求报文主体为空(如GET请求),最后一行应为一个换行符。 签名串中的参数,跟实际请求的参数不一致 手工拼接的URL,和实际请求发送的不一致。我们建议的实现是,使用HTTP库构造请求对象或者URL对象,再使用相应的方法取得URL。 签名和设置Authorization头时,使用了前后生成的两个时间戳 签名和设置Authorization头时,使用了前后生成的两个不同的随机串。 签名和请求时,使用了前后两次序列化的JSON串作为请求主体。
文本的编码不一致 生成签名串使用了非UTF-8编码或者未设置具体编码。 构建签名串顺序不对 构建签名串没有按照文档要求的顺序进行构建。 使用了错误的商户私钥 开发者可以使用如下的openssl命令检查私钥和商户证书中的modulus(p、q两个大素数的乘积)是否一致。如果两者一致,那么私钥和证书是成对的。 1$ openssl x509 -noout -modulus -in 1900009191_20180326_cert.pem 2Modulus=C6D43C87B991... 3$ openssl rsa -noout -modulus -in 1900009191_20180326_key.pem 4Modulus=C6D43C87B991... 5
|
●为什么微信支付的回调缺少签名的几个HTTP头? | |
微信支付的回调,在HTTP头部包含了以下四个HTTP头:
某些代理服务器或CDN服务提供商,转发时会“过滤”微信支付扩展的HTTP头,会导致验签的应用层无法取到微信支付的签名信息。遇到这种情况时,建议商户调整代理服务器配置,或者通过直连的方式接受微信支付的回调。 |