寫在前面的話
1、簽名生成:當請求微信支付API時,簽名不通過,無法使用API接口(使用API證書私鑰加密)
官方文檔:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
2、驗證簽名:當微信支付回調的時,校驗微信請求的簽名,防止惡意請求(使用平台證書公鑰解密)
官方文檔:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
3、簽名生成微信官方給了示例代碼,可以直接使用(但是是寫死私鑰的,本文是從證書.p12文件中讀取);驗證簽名微信官方沒有給.NET的示例代碼
一、簽名生成
源碼:
/// <summary> /// 構造簽名串 /// </summary> /// <param name="method">HTTP請求方式(全大寫)</param> /// <param name="body">API接口請求參數的json字符串</param> /// <param name="uri">API接口的相對路徑</param> /// <returns></returns> protected string BuildAuthAsync(string method, string body, string uri) { var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n"; string signature = RequestSign(message); return $"mchid=\"{_mchID}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{_serialNo}\",signature=\"{signature}\""; } /// <summary> /// 生成簽名 /// </summary> /// <param name="message"></param> /// <returns></returns> protected string RequestSign(string message) { //加載證書 _apiCertPath API證書物理路徑 _certPwd API證書密碼(默認是商戶號) X509Certificate2 cer = new X509Certificate2(_apiCertPath, _certPwd, X509KeyStorageFlags.Exportable); if (cer != null) { RSA rsa = cer.GetRSAPrivateKey(); //獲取私鑰 //查看在不同平台上的具體類型 byte[] data = Encoding.UTF8.GetBytes(message); return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); } else { return ""; } }
簽名使用: 請求API接口時,通過HTTP的Authorization頭傳遞簽名
二、驗證簽名
源碼:
/// <summary> /// 驗證簽名 /// </summary> /// <param name="serialNumber">證書序列號</param> /// <param name="timestamp">時間戳</param> /// <param name="nonce">隨機串</param> /// <param name="body"></param> /// <param name="sign">簽名</param> /// <returns></returns> public bool SignVerify(string serialNumber, string timestamp, string nonce, string body, string sign) { try { string message = $"{timestamp}\n{nonce}\n{body}\n"; string certPath = string.Format(_wechatCertPath, serialNumber); //certPath 平台證書路徑 if (!File.Exists(certPath)) { LogHelper.Error($"平台證書不存在:{certPath},證書序列號:{serialNumber}", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum()); return false; } X509Certificate2 cert = new X509Certificate2(certPath); //加載平台證書 return SignVerify(cert, timestamp, nonce, body, sign); //重載方法 } catch (Exception ex) { LogHelper.Error($"簽名驗證出現異常:{ex.Message}", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum()); return false; } } /// <summary> /// 驗證簽名 /// </summary> /// <param name="cert">證書對象</param> /// <param name="timestamp">時間戳</param> /// <param name="nonce">隨機串</param> /// <param name="body"></param> /// <param name="sign">簽名</param> /// <returns></returns> public bool SignVerify(X509Certificate2 cert, string timestamp, string nonce, string body, string sign) { try { string message = $"{timestamp}\n{nonce}\n{body}\n"; if (cert != null) { byte[] data = Encoding.UTF8.GetBytes(message); var rsaParam = cert.GetRSAPublicKey().ExportParameters(false); var rsa = new RSACryptoServiceProvider(); rsa.ImportParameters(rsaParam); var isOk = rsa.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(sign)); if (!isOk) { LogHelper.Error($"簽名校驗失敗:", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum()); return false; } return true; } else { LogHelper.Error($"證書對象X509Certificate2為空", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum()); return false; } } catch (Exception ex) { LogHelper.Error($"簽名驗證出現異常:{ex.Message}", LogHelper.GetCurSourceFileName(), LogHelper.GetLineNum()); return false; } }
驗簽過程:
1、根據回調參數的平台證書序列號判斷,當前本地證書是否有效,如無效,需要重新獲取證書(可以參考上一篇文章:.NET Core微信支付V3平台證書下載(包含簽名驗證))
2、再進行簽名驗證
寫在結尾的話
之前開發過程中被驗證簽名困擾過,所以記錄一下,方便日后查閱
對於本文有疑問可以聯系我(1217445199@qq.com),歡迎交流~
轉載請注明出處,謝謝~