java微信v3签名验证
商户可以按照下述步骤验证应答或者回调的签名。
如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名。我们建议商户验证应答签名。
同样的,微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须 验证回调的签名,以确保回调是由微信支付发送。
获取平台证书
微信支付API v3使用微信支付 的平台私钥(不是商户私钥 )进行应答签名。相应的,商户的技术人员应使用微信支付平台证书中的公钥验签。目前平台证书只提供API进行下载,请参考 获取平台证书列表。
检查平台证书序列号
微信支付的平台证书序列号位于HTTP头Wechatpay-Serial
。验证签名前,请商户先检查序列号是否跟商户当前所持有的 微信支付平台证书的序列号一致。如果不一致,请重新获取证书。否则,签名的私钥和证书不匹配,将无法成功验证签名。
构造验签名串
首先,商户先从应答中获取以下信息。
- HTTP头
Wechatpay-Timestamp
中的应答时间戳。 - HTTP头
Wechatpay-Nonce
中的应答随机串 - 应答主体(response Body)
然后,请按照以下规则构造应答的验签名串。签名串共有三行,行尾以\n
结束,包括最后一行。\n
为换行符(ASCII编码值为0x0A)。若应答报文主体为空(如HTTP状态码为204 No Content
),最后一行仅为一个\n
换行符。
应答时间戳\n
应答随机串\n
应答报文主体\n
如某个应答的HTTP报文为(省略了ciphertext的具体内容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | HTTP/ 1.1 200 OK Server: nginx Date: Tue, 02 Apr 2019 12 : 59 : 40 GMT Content-Type: application/json; charset=utf- 8 Content-Length: 2204 Connection: keep-alive Keep-Alive: timeout= 8 Content-Language: zh-CN Request-ID: e2762b10-b6b9- 5108 -a42c-16fe2422fc8a Wechatpay-Nonce: c5ac7061fccab6bf3e254dcf98995b8c Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA== Wechatpay-Timestamp: 1554209980 Wechatpay-Serial: 5157F09EFDC096DE15EBE81A47057A7232F1B8E1 Cache-Control: no-cache, must-revalidate { "data" :[{ "serial_no" : "5157F09EFDC096DE15EBE81A47057A7232F1B8E1" , "effective_time" : "2018-03-26T11:39:50+08:00" , "expire_time" : "2023-03-25T11:39:50+08:00" , "encrypt_certificate" :{ "algorithm" : "AEAD_AES_256_GCM" , "nonce" : "4de73afd28b6" , "associated_data" : "certificate" , "ciphertext" : "..." }}]} |
则验签名串为
1 2 3 | 1554209980 c5ac7061fccab6bf3e254dcf98995b8c { "data" :[{ "serial_no" : "5157F09EFDC096DE15EBE81A47057A7232F1B8E1" , "effective_time" : "2018-03-26T11:39:50+08:00" , "expire_time" : "2023-03-25T11:39:50+08:00" , "encrypt_certificate" :{ "algorithm" : "AEAD_AES_256_GCM" , "nonce" : "4de73afd28b6" , "associated_data" : "certificate" , "ciphertext" : "..." }}]} |
获取应答签名
微信支付的应答签名通过HTTP头Wechatpay-Signature
传递。(注意,示例因为排版可能存在换行,实际数据应在一行)
1 | Wechatpay-Signature: CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA== |
对Wechatpay-Signature
的字段值使用Base64进行解码,得到应答签名。
验证签名
很多编程语言的签名验证函数支持对验签名串和签名 进行签名验证。强烈建议商户调用该类函数,使用微信支付平台公钥对验签名串和签名进行SHA256 with RSA签名验证。
下面展示使用命令行演示如何进行验签。假设我们已经获取了平台证书并保存为1900009191_wxp_cert.pem
。
首先,从微信支付平台证书导出微信支付平台公钥
1 2 3 4 5 6 7 8 | $ openssl x509 -in 1900009191_wxp_cert.pem -pubkey -noout > 1900009191_wxp_pub.pem $ cat 1900009191_wxp_pub.pem -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4zej1cqugGQtVSY2Ah8R MCKcr2UpZ8Npo+5Ja9xpFPYkWHaF1Gjrn3d5kcwAFuHHcfdc3yxDYx6+9grvJnCA 2zQzWjzVRa3BJ5LTMj6yqvhEmtvjO9D1xbFTA2m3kyjxlaIar/RYHZSslT4VmjIa tW9KJCDKkwpM6x/RIWL8wwfFwgz2q3Z2nB8p8P12ndH7GSLoY6d2Tv0OB2+We2Ky<br>y2+QzfGXOmLp7UK/pFQjJjzhSf9jxaWJXYKIBxpGlddbRZj9PqvFPTiep8rvfKGN<br>ZF9Q6QaMYTpTp/uKQ3YvpDlyeQlYe4rRFauH3mOE6j56QlYQWivknDX9VrwIDAQA<br>BKADASWQWC -----END PUBLIC KEY----- |
然后,把签名base64解码后保存为文件signature.txt
1 | $ openssl base64 -d -A <<< \ 'CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==' > signature.txt |
最后,验证签名
1 2 3 4 5 6 | $ openssl dgst -sha256 -verify 1900009191_wxp_pub.pem -signature signature.txt << EOF 1554209980 c5ac7061fccab6bf3e254dcf98995b8c { "data" :[{ "serial_no" : "5157F09EFDC096DE15EBE81A47057A7232F1B8E1" , "effective_time" : "2018-03-26T11:39:50+08:00" , "expire_time" : "2023-03-25T11:39:50+08:00" , "encrypt_certificate" :{ "algorithm" : "AEAD_AES_256_GCM" , "nonce" : "d215b0511e9c" , "associated_data" : "certificate" , "ciphertext" : "..." }}]} EOF Verified OK |
代码实现
/** * 验证签名 * * @param timestamp 微信平台传入的时间戳 * @param nonce 微信平台传入的随机字符串 * @param requestBody 微信平台传入的消息体 * @param signature 微信平台传入的签名 * @return * @throws NoSuchAlgorithmException * @throws SignatureException * @throws IOException * @throws InvalidKeyException */ public static boolean signCheck(String timestamp, String nonce, Map<String, Object> requestBody, String signature) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException { //构造验签名串 String signatureStr = timestamp + "\n" + nonce + "\n" + JSONObject.toJSONString(requestBody) + "\n"; // 加载SHA256withRSA签名器 Signature signer = Signature.getInstance("SHA256withRSA"); // 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法) signer.initVerify(CommonUtils.getCertificates()); // 把我们构造的验签名串更新到签名器中 signer.update(signatureStr.getBytes(StandardCharsets.UTF_8)); // 把请求头中微信服务器返回的签名用Base64解码 并使用签名器进行验证 boolean result = signer.verify(Base64Utils.decodeFromString(signature)); return result; }
最后来个完整实例:
/** * 回调地址 * * @param requestBody * @return * @throws IOException */ @PostMapping("/api/notify/complaintsNotify") public Map<String, String> complaintsNotify(HttpServletRequest request, @RequestBody Map<String, Object> requestBody) throws Exception { Map<String, String> data = new HashMap<>(); String signature = request.getHeader("Wechatpay-Signature"); String timestamp = request.getHeader("Wechatpay-Timestamp"); String nonce = request.getHeader("Wechatpay-Nonce"); //平台证书序列号不是API证书序列号 String serial = request.getHeader("Wechatpay-Serial"); log.info("头信息---签名:" + signature); log.info("头信息---时间戳:" + timestamp); log.info("头信息---随机字符:" + nonce); log.info("头信息---平台证书序列号:" + serial); log.info("获取到的body信息:" + JSONObject.toJSONString(requestBody)); //验签 boolean signCheck = SignUtils.signCheck(timestamp, nonce, requestBody, signature); log.info("验签结果:" + signCheck); if (signCheck) { //解密参数 Resource resource = JSONObject.parseObject(JSONObject.toJSONString(requestBody.get("resource")), Resource.class); AesUtil aesUtil = new AesUtil(CommonParameters.apiV3Key.getBytes("utf-8")); String string = aesUtil.decryptToString(resource.getAssociated_data().getBytes("utf-8"), resource.getNonce().getBytes("utf-8"), resource.getCiphertext()); ComplaintInfo complaintInfo = JSONObject.parseObject(string, ComplaintInfo.class); //获取投诉详情 ComplaintDetail complaintDetail = CommonUtils.GetComplaintsInfo(complaintInfo.getComplaint_id()); data.put("code", "SUCCESS"); data.put("message", "成功"); return data; } return data; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具