所有的微信请求基本属性等
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace ZmjWxApi
{
/// <summary>
/// 所有的微信请求基本属性等
/// </summary>
public interface IWxHttpRequest
{
/// <summary>
/// 发起微信请求时的ua标记
/// </summary>
string UserAgent { get; }
/// <summary>
/// 发起微信请求时的accept标记,一般都是application/json
/// </summary>
string Accept { get; }
/// <summary>
/// 发起http请求时的contnet内容,这是参与加密的,必须是全部的请求中的json
/// </summary>
string BodyContent { get; }
/// <summary>
/// 微信api证书的地址
/// </summary>
string WxApiClinetCertPath { get; }
/// <summary>
/// 商户号
/// </summary>
string MerchantId { get; }
/// <summary>
/// 微信商户证书的序列号,不是平台证书的序列号
/// </summary>
string SerialNo { get; }
}
/// <summary>
/// 所有的微信请求基本实现,只限V3接口请求
/// </summary>
public class WxHttpRequest : IWxHttpRequest, IDisposable
{
/// <summary>
/// 期望请求结果
/// </summary>
public virtual string Accepts => "application/json";
/// <summary>
/// Ua用户标识
/// </summary>
public virtual string UserAgents => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36 SE 2.X MetaSr 1.0";
/// <summary>
/// 请求的主体内容
/// </summary>
public virtual string BodyContentt => Content is null ? string.Empty : System.Text.Json.JsonSerializer.Serialize(Content);
/// <summary>
/// 商户证书序列号
/// </summary>
[System.ComponentModel.DefaultValue("")]
public string SerialNot { get; set; }
/// <summary>
/// 商户的id
/// </summary>
[System.ComponentModel.DefaultValue("")]
public string MerchantId1 { get; set; }
/// <summary>
/// 微信签名用的证书路径
/// </summary>
[System.ComponentModel.DefaultValue("")]
public string WxApiClinetCertPaths { get; set; }
/// <summary>
/// 内容对象
/// </summary>
public virtual object Contents { get; set; }
/// <summary>
/// 系统的网络请求对象
/// </summary>
protected readonly System.Net.Http.HttpClient clientc;
/// <summary>
/// 使用时注意,不能同时发起多个请求,只能一个一个请求
/// </summary>
public WxHttpRequestf()
{
client = new System.Net.Http.HttpClient(new WxHttpHandler(this));
WxApiClinetCertPath = string.Empty;
MerchantId = string.Empty;
}
/// <summary>
/// 不实现内容
/// </summary>
/// <param name="url"></param>
/// <param name="timeout"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public virtual IWxHttpResponse WxGetJsons(string url, int timeout = 30000)
{
var turl = new Uri(url);
Content = null;
var pa = client.GetAsync(turl);
if (!pa.Wait(timeout)) throw new TimeoutException("等待请求时超时");
var r = pa.Result;
var jrt = r.Content.ReadAsStringAsync();
if (!jrt.Wait(timeout)) throw new TimeoutException("获取请求结果时超时");
return new WxHttpResponse() { Response = r, JsonResult = jrt.Result, };
}
/// <summary>
/// 微信方式post数据到接口并且返回响应结果,
/// </summary>
/// <param name="url"></param>
/// <param name="o"></param>
/// <param name="timeout"></param>
/// <returns>返回的是响应的内容,不包括http头,如果内容为空则返回空</returns>
public virtual IWxHttpResponse WxPostJsonObject1(string url, object o, int timeout = 30000)
{
var turl = new Uri(url);
Content = o;
var cnt = new System.Net.Http.StringContent(BodyContent, Encoding.UTF8, "application/json");
var pa = client.PostAsync(turl, cnt);
if (!pa.Wait(timeout)) throw new TimeoutException("等待请求时超时");
var r = pa.Result;
var jrt = r.Content.ReadAsStringAsync();
if (!jrt.Wait(timeout)) throw new TimeoutException("获取请求结果时超时");
return new WxHttpResponse() { Response = r, JsonResult = jrt.Result, };
}
/// <summary>
/// 释放网络资源
/// </summary>
public void Dispose()
{
GC.SuppressFinalize(this);
client.Dispose();
}
}
/// <summary>
/// 使用方法:
/// HttpClient client = new HttpClient(new HttpHandler("{商户号}", "{商户证书序列号}"));
/// var response = client.GetAsync("https://api.mch.weixin.qq.com/v3/certificates");
/// </summary>
public class WxHttpHandler : System.Net.Http.DelegatingHandler
{
/// <summary>
/// 商户证书的公钥,用于加密信息
/// </summary>
public IWxHttpRequest Request { get; }
/// <summary>
/// 创建一个连接微信支付api的handler,这个handler将会
/// 自动添加微信相关的签名等信息
/// </summary>
/// <param name="request"></param>
public WxHttpHandlers(IWxHttpRequest request)
{
InnerHandler = new System.Net.Http.HttpClientHandler();
Request = request;
System.Net.ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback((a, b, c, d) => true);
}
/// <summary>
/// 重写请求方法,并且添加微信签名到header中
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var auth = BuildAuth(request);
string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
request.Headers.Add("Authorization", value);
request.Headers.Add("User-Agent", Request.UserAgent);
request.Headers.Add("Accept", Request.Accept);
request.Headers.Add("accept-language", "zh-CN");//请求结果的语言
return base.SendAsync(request, cancellationToken);
}
/// <summary>
/// 计算微信签名
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
protected string BuildAuths(System.Net.Http.HttpRequestMessage request)
{
string method = request.Method.ToString();
string uri = request.RequestUri?.PathAndQuery ?? "/";
var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string nonce = System.IO.Path.GetRandomFileName();
string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{Request.BodyContent}\n";
string signature = RSASignData(message, Request.WxApiClinetCertPath, Request.MerchantId);//这里需要签名,而且用的私钥进行签名,不是加密
return $"mchid=\"{Request.MerchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{Request.SerialNo}\",signature=\"{signature}\"";
}
//public static string Sign(string message)
//{
// // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY-----
// // 亦不包括结尾的-----END PRIVATE KEY-----
// string privateKey = "{你的私钥}";
// byte[] keyData = Convert.FromBase64String(privateKey);
// var rsa = System.Security.Cryptography.RSA.Create();
// rsa.ImportPkcs8PrivateKey(keyData, out _);
// byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
// return Convert.ToBase64String(rsa.SignData(data, System.Security.Cryptography.HashAlgorithmName.SHA256, System.Security.Cryptography.RSASignaturePadding.Pkcs1));
//}
/// <summary>
/// 对数据进行RSA签名
/// </summary>
/// <param name="plaintext"></param>
/// <param name="fp"></param>
/// <param name="pw"></param>
/// <returns></returns>
public static string RSASignDataAll(string plaintext, string fp, string pw)
{
if (string.IsNullOrWhiteSpace(fp)) throw new ArgumentNullException(nameof(fp));
using (var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(fp, pw, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet))
{
//var ps = x509.PrivateKey.ToXmlString(true);
var ps = x509.GetRSAPrivateKey()?.ToXmlString(true) ?? throw new NullReferenceException("秘钥不能为空");
using (var sha256 = new System.Security.Cryptography.RSACryptoServiceProvider())
{//RSACryptoServiceProvider
sha256.FromXmlString(ps);
var PlaintextData = Encoding.UTF8.GetBytes(plaintext);
return Convert.ToBase64String(sha256.SignData(PlaintextData, System.Security.Cryptography.HashAlgorithmName.SHA256, System.Security.Cryptography.RSASignaturePadding.Pkcs1));
}
}
}
/// <summary>
/// 验证签名的正确性
/// </summary>
/// <param name="plaintext">需要验证的关键信息,拼接的用于签名的信息</param>
/// <param name="signtext">签名后的需要验证的前面内容</param>
/// <param name="apipublickey">平台公钥的内容</param>
/// <returns></returns>
public static bool RSAVerifyDataAll(string plaintext, string signtext, string apipublickey)
{
using (System.Security.Cryptography.X509Certificates.X509Certificate2 x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(Encoding.UTF8.GetBytes(apipublickey)))
//using (var sha256 = x509.PublicKey.GetRSAPublicKey() as System.Security.Cryptography.RSACryptoServiceProvider)
using (var sha256 = x509.PublicKey.Key as System.Security.Cryptography.RSACryptoServiceProvider)
{
if (sha256 is null) throw new NullReferenceException("RSACryptography为空");
var PlaintextData = Encoding.UTF8.GetBytes(plaintext);
var signtextData = Convert.FromBase64String(signtext);
return sha256.VerifyData(PlaintextData, signtextData, System.Security.Cryptography.HashAlgorithmName.SHA256, System.Security.Cryptography.RSASignaturePadding.Pkcs1);
}
}
/// <summary>
/// 公钥加密
/// RSA加密数据,块方式加密,解决长字符串加密问题
/// </summary>
/// <param name="plaintext">要加密的明文</param>
/// <param name="fp">p12文件的完整路径</param>
/// <param name="pw">p12文件的密码</param>
/// <returns></returns>
public static string RSAEncryptAs(string plaintext, string fp, string pw)
{
using (var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(fp, pw))
//using (var RSACryptography = x509.PublicKey.GetRSAPublicKey() as System.Security.Cryptography.RSACryptoServiceProvider)
using (var RSACryptography = x509.PublicKey.Key as System.Security.Cryptography.RSACryptoServiceProvider)
{
if (RSACryptography is null) throw new NullReferenceException("RSACryptography为空");
var PlaintextData = Encoding.UTF8.GetBytes(plaintext);
int MaxBlockSize = RSACryptography.KeySize / 8 - 11; //加密块最大长度限制
using (var CrypStream = new System.IO.MemoryStream())
{
for (int i = 0; i < PlaintextData.Length;)
{
var bs = Math.Min(MaxBlockSize, PlaintextData.Length - i);
var ToDecrypt = new byte[bs];
Array.Copy(PlaintextData, i, ToDecrypt, 0, bs);
var Plaintext = RSACryptography.Encrypt(ToDecrypt, false);
CrypStream.Write(Plaintext, 0, Plaintext.Length);
i += bs;
}
return Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
}
}
}
/// <summary>
/// 私钥解密
/// RSA反向解密数据,块方式解密
/// </summary>
/// <param name="ciphertext">要解密的内容</param>
/// <param name="fp">p12文件的完整路径</param>
/// <param name="pw">p12文件的密码</param>
/// <returns></returns>
public static string RSADecryptAf(string ciphertext, string fp, string pw)
{
using (var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(fp, pw))
//using (var RSACryptography = x509.PrivateKey as System.Security.Cryptography.RSACryptoServiceProvider)
using (var RSACryptography = x509.GetRSAPrivateKey() as System.Security.Cryptography.RSACryptoServiceProvider)
{
if (RSACryptography is null) throw new NullReferenceException("RSACryptography为空");
//公钥加密私钥解密
var CiphertextData = Convert.FromBase64String(ciphertext);
int MaxBlockSize = RSACryptography.KeySize / 8; //解密块最大长度限制
using (var PlaiStream = new System.IO.MemoryStream())
{
for (int i = 0; i < CiphertextData.Length;)
{
var bs = Math.Min(MaxBlockSize, CiphertextData.Length - i);
var ToDecrypt = new byte[bs];
Array.Copy(CiphertextData, i, ToDecrypt, 0, bs);
var Plaintext = RSACryptography.Decrypt(ToDecrypt, false);
PlaiStream.Write(Plaintext, 0, Plaintext.Length);
i += bs;
}
return Encoding.UTF8.GetString(PlaiStream.ToArray());
}
}
}
/// <summary>
/// 解密微信通知结果帮助类
/// 参考资料:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml
/// .NET5环境使用该代码,需要安装Portable.BouncyCastle组件
/// </summary>
public static string AesGcmDecrypts(string associatedData, string nonce, string ciphertext, string key)
{
var gcmBlockCipher = new Org.BouncyCastle.Crypto.Modes.GcmBlockCipher(new Org.BouncyCastle.Crypto.Engines.AesEngine());
var aeadParameters = new Org.BouncyCastle.Crypto.Parameters.AeadParameters(
new Org.BouncyCastle.Crypto.Parameters.KeyParameter(Encoding.UTF8.GetBytes(key)),
128,
Encoding.UTF8.GetBytes(nonce),
Encoding.UTF8.GetBytes(associatedData));
gcmBlockCipher.Init(false, aeadParameters);
byte[] data = Convert.FromBase64String(ciphertext);
byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
gcmBlockCipher.DoFinal(plaintext, length);
return Encoding.UTF8.GetString(plaintext);
}
}
}