微信支付官方文档对于签名验证的介绍:微信支付-开发者文档 (qq.com)
微信支付会在回调的HTTP头部中包括回调报文的签名。商户必须 验证回调的签名,以确保回调是由微信支付发送。
一个支付回调的示例:
Headers:
[{"Key":"Connection","Value":["Keep-Alive"]},{"Key":"Pragma","Value":["no-cache"]},{"Key":"Accept","Value":["*/*"]},{"Key":"Host","Value":["www.datatook.com"]},{"Key":"User-Agent","Value":["Mozilla/4.0"]},{"Key":"Wechatpay-Nonce","Value":["lL0M9v8R3WtTa5iusxEWgjXgjbyQeP3D"]},{"Key":"Wechatpay-Serial","Value":["4B771705B6FFCA007AAE05A3512E4EA923BF757E"]},{"Key":"Wechatpay-Signature","Value":["ImfeCH3vkB/iQ+nl+JSBuzFdx5aZf3V9+CAyqSww/xeavzcTGtqCO+bixh9Lylk0ZZ2/UJOfJd/KWL4BCqw+GMumAHVWMzb34a0kAdHimVuUt36dNz2ZI58mM/1RN75AMYuNcCptXPTgHzdGBl3+bE2VO+dnoK5q/X+lskFDcxb0LGvKVA7V/bVA0edhSBCaUeiEogajXVXDmHcaHk/4/fUmDD2NCJwjVh0CdynC5p6IQN+mkyITwlJQgASgN6+truAGFGnN50FunTVPXGLze/VbFbZg87flawWce+/KpmFqfSvhxyUOQ/axPMvZkSvMxIWCp7Y0kw6MfMXMCCP5Ow=="]},{"Key":"Wechatpay-Timestamp","Value":["1622016489"]}]
Body:
{"id":"ebd1442a-156f-5e1f-8ca4-d43bf679a603","create_time":"2021-05-26T16:08:09+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"vocEJn2O7MWb9d21Zk48EiHuZfQMapbAjgHPPsvYisfHkZKs4jF15joWbrKb37HYbDuYVYgPoUta4u3i2c1YrcA9VxGWrO043dUO3Nj2QHURCEnIE9j+3G7iY+3PPYiJTI3M1w5vRw6+BDNn0atcLr5GuOy0ajvxpr7bu3GJa6fjuO23pbWw/rEfALaH3igl1gQeeSm+zkVmNeA5uXFrlmX8gMQkADDs+YVIKC84rBslyJZfhvX9rqpI+zPGtXJPs9lRM4qsbtVsqWMK+nEnB7wfj9qTa3fHh/lBtNXzCB29tlodzBPKQS+Pxa9GCMtf3HeIf8YognqF496eqq3kPlGEcF3BdqVu1VqzdW1e+9qzPu68GfexTkml8GliCdFdaO7eq9BUOoebulOLq/p58ZxORxoeaCKR3NfItmSVyHIPbS3wlulJgUDWFo0Umt4hkSmxRp4s+4P40Pc+w/tfAkQsQUYln7V1c1XR8IpWrQn22YGIOKGqB86Fs8Bw4F8S7QzKVhCbD075AoiRhGciXpFD6XNYiMF0gk7gB6eUooZj3aU1bkJzzmf29xUJktcK6G/IeLipE/KzKVeOJJ9t/bqjJRtbAqfEUnTsf7xgwZ2MjQ9gnFn4XG1TELypAyJnajnXfjBcwmEU/3oMxKDw35w9Oc+5V501JFmN/58iKSWYzFK9e48C6iRgZ5cOuU0ImAyF2rjF0KhofJwy7gDV19SteyJkzH+m+116ZiEa8iki","associated_data":"transaction","nonce":"C8EMTkPpAvH5"}}
在回调签名验证中需要用到几个参数:
serialNo
:平台证书(公钥)序列号,验证签名需要用到平台公钥 PublicKey
,通过 获取平台证书列表(https://api.mch.weixin.qq.com/v3/certificates) 接口获取,PublicKey
下载下来后保存在自己的服务器中和 serialNo
是一对,通过 serialNo
找到公钥,如果和保存的公钥 serialNo
不匹配,代表微信平台证书已经更新,需要重新下载平台公钥。在 Header 中获得,参数为:Wechatpay-Serial
示例中值为 4B771705B6FFCA007AAE05A3512E4EA923BF757E
timestamp
:签名时间戳,在 Header 中获得参数为 Wechatpay-Timestamp
示例中值为 1622016489
nonce
:签名随机字符串,在 Header 中获得,参数为 Wechatpay-Nonce
示例中值为 lL0M9v8R3WtTa5iusxEWgjXgjbyQeP3D
signature
:签名,在 Header 中获得,参数为 Wechatpay-Signature
示例中值为 ImfeCH3vkB/iQ+nl+JSBuzFdx5aZf3V9+CAyqSww/xeavzcTGtqCO+bixh9Lylk0ZZ2/UJOfJd/KWL4BCqw+GMumAHVWMzb34a0kAdHimVuUt36dNz2ZI58mM/1RN75AMYuNcCptXPTgHzdGBl3+bE2VO+dnoK5q/X+lskFDcxb0LGvKVA7V/bVA0edhSBCaUeiEogajXVXDmHcaHk/4/fUmDD2NCJwjVh0CdynC5p6IQN+mkyITwlJQgASgN6+truAGFGnN50FunTVPXGLze/VbFbZg87flawWce+/KpmFqfSvhxyUOQ/axPMvZkSvMxIWCp7Y0kw6MfMXMCCP5Ow==
body
:post请求的主体 content
// 平台证书序列号
string serialNo = Request.Headers.GetValues("Wechatpay-Serial").FirstOrDefault();
// 签名时间戳
string timestamp = Request.Headers.GetValues("Wechatpay-Timestamp").FirstOrDefault();
// 签名随机字符串
string nonce = Request.Headers.GetValues("Wechatpay-Nonce").FirstOrDefault();
// 验签值
string signature = Request.Headers.GetValues("Wechatpay-Signature").FirstOrDefault();
Request.Content.ReadAsStreamAsync().Result.Seek(0, System.IO.SeekOrigin.Begin);
// POST请求主体
string body = Request.Content.ReadAsStringAsync().Result;
签名时间戳\n
签名随机串\n
Post报文主体\n
代码:
string message = $"{timestamp}\n{nonce}\n{body}\n";
示例中结果是:
1622016489\n
lL0M9v8R3WtTa5iusxEWgjXgjbyQeP3D\n
{"id":"ebd1442a-156f-5e1f-8ca4-d43bf679a603","create_time":"2021-05-26T16:08:09+08:00","resource_type":"encrypt-resource","event_type":"TRANSACTION.SUCCESS","summary":"支付成功","resource":{"original_type":"transaction","algorithm":"AEAD_AES_256_GCM","ciphertext":"vocEJn2O7MWb9d21Zk48EiHuZfQMapbAjgHPPsvYisfHkZKs4jF15joWbrKb37HYbDuYVYgPoUta4u3i2c1YrcA9VxGWrO043dUO3Nj2QHURCEnIE9j+3G7iY+3PPYiJTI3M1w5vRw6+BDNn0atcLr5GuOy0ajvxpr7bu3GJa6fjuO23pbWw/rEfALaH3igl1gQeeSm+zkVmNeA5uXFrlmX8gMQkADDs+YVIKC84rBslyJZfhvX9rqpI+zPGtXJPs9lRM4qsbtVsqWMK+nEnB7wfj9qTa3fHh/lBtNXzCB29tlodzBPKQS+Pxa9GCMtf3HeIf8YognqF496eqq3kPlGEcF3BdqVu1VqzdW1e+9qzPu68GfexTkml8GliCdFdaO7eq9BUOoebulOLq/p58ZxORxoeaCKR3NfItmSVyHIPbS3wlulJgUDWFo0Umt4hkSmxRp4s+4P40Pc+w/tfAkQsQUYln7V1c1XR8IpWrQn22YGIOKGqB86Fs8Bw4F8S7QzKVhCbD075AoiRhGciXpFD6XNYiMF0gk7gB6eUooZj3aU1bkJzzmf29xUJktcK6G/IeLipE/KzKVeOJJ9t/bqjJRtbAqfEUnTsf7xgwZ2MjQ9gnFn4XG1TELypAyJnajnXfjBcwmEU/3oMxKDw35w9Oc+5V501JFmN/58iKSWYzFK9e48C6iRgZ5cOuU0ImAyF2rjF0KhofJwy7gDV19SteyJkzH+m+116ZiEa8iki","associated_data":"transaction","nonce":"C8EMTkPpAvH5"}}\n
serialNo
是否一致对比 Header
中的 平台序列号
( serialNo
) 和 本地平台公钥
的 serialNo
是否一致
一致
:直接返回
本地的平台公钥
不一致
:调用 获取平台证书列表(https://api.mch.weixin.qq.com/v3/certificates) 接口 获取
新的 平台公钥
和 serialNo
, 并 确保 和 Header 中的 serialNo 一致,保存
到本地,返回公钥
public class WXCertHelper
{
List<WXPlatformPublicKey> CacheData { get; set; }
private WXCertHelper()
{
CacheData = new List<WXPlatformPublicKey>();
}
static WXCertHelper _intance;
// 单例模式
public static WXCertHelper Intance
{
get
{
if (_intance == null)
{
_intance = new WXCertHelper();
_intance.LoadCacheData();
}
return _intance;
}
}
/// <summary>
/// 从本地加载平台序列号
/// </summary>
void LoadCacheData()
{
CacheData.Clear();
// 从数据库加载数据
}
/// <summary>
/// 保存平台公钥到本地
/// </summary>
/// <param name="data"></param>
void SaveCacheData()
{
//List<WXPlatformPublicKey> data = this.CacheData;
// 刷新本地存储
}
public string GetPublicKey(string serialNo)
{
// 匹配缓存中的公钥
var obj = CacheData.Find(w => w.serialNo == serialNo);
if (obj != null)
{
return obj.PublicKey;
}
// 缓存中没有,重新下载
else
{
this.RefreshDownCert();
return GetPublicKey(serialNo);
}
}
/// <summary>
/// 重新从API下载平台公钥
/// </summary>
void RefreshDownCert()
{
APIHelper apiHelper = new APIHelper();
CacheData.Clear();
var result = apiHelper.DownCert();
foreach (var o in result.data)
{
string publicKey = AesGcm.AesGcmDecrypt(o.encrypt_certificate.associated_data, o.encrypt_certificate.nonce, o.encrypt_certificate.ciphertext, ConfigData.Intance.WXPaySetting.wx_aes_key);
CacheData.Add(new WXPlatformPublicKey()
{
serialNo = o.serial_no,
PublicKey = publicKey
});
}
SaveCacheData();
}
}
public class WXPlatformPublicKey
{
/// <summary>
/// 平台证书序列号
/// </summary>
public string serialNo { get; set; }
/// <summary>
/// 平台证书公钥
/// </summary>
public string PublicKey { get; set; }
}
/// <summary>
/// 验证签名
/// </summary>
/// <param name="message">签名字符串</param>
/// <param name="publickey">公钥</param>
/// <param name="signStr">签名</param>
/// <returns></returns>
public bool VerifySign(string message, string publickey, string signStr)
{
try
{
var _publicKeyBytes = Encoding.UTF8.GetBytes(publickey);
var x509 = new X509Certificate2(_publicKeyBytes);
using (var rsa = (RSACryptoServiceProvider)x509.PublicKey.Key)
{
using (var sha256 = new SHA256CryptoServiceProvider())
{
var b = rsa.VerifyData(Encoding.UTF8.GetBytes(message), sha256, Convert.FromBase64String(signStr));
return b;
}
}
}
catch { return false; }
}
var _publicKeyBytes = Encoding.UTF8.GetBytes(publickey);
var x509 = new X509Certificate2(_publicKeyBytes);
using (var rsa = (System.Security.Cryptography.RSACng)x509.PublicKey.Key)
{
rsa.VerifyData(Encoding.UTF8.GetBytes(message), Convert.FromBase64String(signStr), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}