微信公众号开发--用.Net Core实现微信消息加解密

发布时间:2018/1/9 17:43:06 次浏览

1、进入微信公众号后台设置微信服务器配置参数(注意:Token和EncodingAESKey必须和微信服务器验证参数保持一致,不然验证不会通过)。



2、设置为安全模式



3、代码实现(主要分为验证接口和消息处理接口):

复制代码
 1 /// <summary>
 2 /// 验证接口
 3 /// </summary>
 4 /// <param name="signature">签名</param>
 5 /// <param name="timestamp">时间戳</param>
 6 /// <param name="nonce"></param>
 7 /// <param name="echostr"></param>
 8 /// <returns></returns>
 9 [HttpGet, Route("Message")]
10 [AllowAnonymous]
11 public ActionResult MessageGet(string signature, string timestamp, string nonce, string echostr)
12 {
13 if (new SecurityHelper().CheckSignature(signature, timestamp, nonce, _settings.Value.Token))
14 {
15 return Content(echostr);
16 }
17 return Content("");
18 }
19 
20 /// <summary>
21 /// 接收消息并处理和返回相应结果
22 /// </summary>
23 /// <param name="msg_signature">当加密模式时才会有该变量(消息签名)</param>
24 /// <param name="signature">签名</param>
25 /// <param name="timestamp">时间戳</param>
26 /// <param name="nonce"></param>
27 /// <returns></returns>
28 [HttpPost, Route("Message")]
29 [AllowAnonymous]
30 public ActionResult MessagePost(string msg_signature, string signature, string timestamp, string nonce)
31 {
32 try
33 {
34 if (!new SecurityHelper().CheckSignature(signature, timestamp, nonce, _settings.Value.Token))
35 {
36 return Content(null);
37 }
38 using (Stream stream = HttpContext.Request.Body)
39 {
40 byte[] buffer = new byte[HttpContext.Request.ContentLength.Value];
41 stream.Read(buffer, 0, buffer.Length);
42 string content = Encoding.UTF8.GetString(buffer);
43 if (!string.IsNullOrWhiteSpace(msg_signature)) // 消息加密模式
44 {
45 string decryptMsg = string.Empty;
46 var wxBizMsgCrypt = new WXBizMsgCrypt(_settings.Value.Token, _settings.Value.EncodingAESKey, _settings.Value.AppId);
47 int decryptResult = wxBizMsgCrypt.DecryptMsg(msg_signature, timestamp, nonce, content, ref decryptMsg);
48 if (decryptResult == 0 && !string.IsNullOrWhiteSpace(decryptMsg))
49 {
50 string resultMsg = new WechatMessageHelper().MessageResult(decryptMsg);
51 string sEncryptMsg = string.Empty;
52 if (!string.IsNullOrWhiteSpace(resultMsg))
53 {
54 int encryptResult = wxBizMsgCrypt.EncryptMsg(resultMsg, timestamp, nonce, ref sEncryptMsg);
55 if (encryptResult == 0 && !string.IsNullOrWhiteSpace(sEncryptMsg))
56 {
57 return Content(sEncryptMsg);
58 }
59 }
60 }
61 }
62 else // 消息未加密码处理
63 {
64 string resultMsg = new WechatMessageHelper().MessageResult(content);
65 return Content(resultMsg);
66 }
67 return Content(null);
68 }
69 }
70 catch (Exception ex)
71 {
72 _logger.LogError("接收消息并处理和返回相应结果异常:", ex);
73 return Content(null);
74 }
75 }
复制代码
加解密实现(微信公众号官网有源码)

复制代码
  1 using System;
  2 using System.Collections;
  3 using System.Security.Cryptography;
  4 using System.Text;
  5 using System.Xml;
  6 
  7 //-40001 : 签名验证错误
  8 //-40002 :  xml解析失败
  9 //-40003 :  sha加密生成签名失败
 10 //-40004 :  AESKey 非法
 11 //-40005 :  appid 校验错误
 12 //-40006 :  AES 加密失败
 13 //-40007 : AES 解密失败
 14 //-40008 : 解密后得到的buffer非法
 15 //-40009 :  base64加密异常
 16 //-40010 :  base64解密异常
 17 namespace  Core.Common.Wechat
 18 {
 19     public class WXBizMsgCrypt
 20     {
 21         string m_sToken;
 22         string m_sEncodingAESKey;
 23         string m_sAppID;
 24         enum WXBizMsgCryptErrorCode
 25         {
 26             WXBizMsgCrypt_OK = 0,
 27             WXBizMsgCrypt_ValidateSignature_Error = -40001,
 28             WXBizMsgCrypt_ParseXml_Error = -40002,
 29             WXBizMsgCrypt_ComputeSignature_Error = -40003,
 30             WXBizMsgCrypt_IllegalAesKey = -40004,
 31             WXBizMsgCrypt_ValidateAppid_Error = -40005,
 32             WXBizMsgCrypt_EncryptAES_Error = -40006,
 33             WXBizMsgCrypt_DecryptAES_Error = -40007,
 34             WXBizMsgCrypt_IllegalBuffer = -40008,
 35             WXBizMsgCrypt_EncodeBase64_Error = -40009,
 36             WXBizMsgCrypt_DecodeBase64_Error = -40010
 37         };
 38 
 39         //构造函数
 40         // @param sToken: 公众平台上,开发者设置的Token
 41         // @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
 42         // @param sAppID: 公众帐号的appid
 43         public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sAppID)
 44         {
 45             m_sToken = sToken;
 46             m_sAppID = sAppID;
 47             m_sEncodingAESKey = sEncodingAESKey;
 48         }
 49 
 50 
 51         // 检验消息的真实性,并且获取解密后的明文
 52         // @param sMsgSignature: 签名串,对应URL参数的msg_signature
 53         // @param sTimeStamp: 时间戳,对应URL参数的timestamp
 54         // @param sNonce: 随机串,对应URL参数的nonce
 55         // @param sPostData: 密文,对应POST请求的数据
 56         // @param sMsg: 解密后的原文,当return返回0时有效
 57         // @return: 成功0,失败返回对应的错误码
 58         public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
 59         {
 60             if (m_sEncodingAESKey.Length != 43)
 61             {
 62                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
 63             }
 64             XmlDocument doc = new XmlDocument();
 65             XmlNode root;
 66             string sEncryptMsg;
 67             try
 68             {
 69                 doc.LoadXml(sPostData);
 70                 root = doc.FirstChild;
 71                 sEncryptMsg = root["Encrypt"].InnerText;
 72             }
 73             catch (Exception)
 74             {
 75                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
 76             }
 77             //verify signature
 78             int ret = 0;
 79             ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
 80             if (ret != 0)
 81                 return ret;
 82             //decrypt
 83             string cpid = "";
 84             try
 85             {
 86                 sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
 87             }
 88             catch (FormatException)
 89             {
 90                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
 91             }
 92             catch (Exception)
 93             {
 94                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
 95             }
 96             if (cpid != m_sAppID)
 97                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error;
 98             return 0;
 99         }
100 
101         //将企业号回复用户的消息加密打包
102         // @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
103         // @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
104         // @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
105         // @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
106         //                        当return返回0时有效
107         // return:成功0,失败返回对应的错误码
108         public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
109         {
110             if (m_sEncodingAESKey.Length != 43)
111             {
112                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
113             }
114             string raw = "";
115             try
116             {
117                 raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID);
118             }
119             catch (Exception)
120             {
121                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
122             }
123             string MsgSigature = "";
124             int ret = 0;
125             ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
126             if (0 != ret)
127                 return ret;
128             sEncryptMsg = "";
129 
130             string EncryptLabelHead = "<Encrypt><![CDATA[";
131             string EncryptLabelTail = "]]></Encrypt>";
132             string MsgSigLabelHead = "<MsgSignature><![CDATA[";
133             string MsgSigLabelTail = "]]></MsgSignature>";
134             string TimeStampLabelHead = "<TimeStamp><![CDATA[";
135             string TimeStampLabelTail = "]]></TimeStamp>";
136             string NonceLabelHead = "<Nonce><![CDATA[";
137             string NonceLabelTail = "]]></Nonce>";
138             sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
139             sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
140             sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
141             sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
142             sEncryptMsg += "</xml>";
143             return 0;
144         }
145 
146         public class DictionarySort : System.Collections.IComparer
147         {
148             public int Compare(object oLeft, object oRight)
149             {
150                 string sLeft = oLeft as string;
151                 string sRight = oRight as string;
152                 int iLeftLength = sLeft.Length;
153                 int iRightLength = sRight.Length;
154                 int index = 0;
155                 while (index < iLeftLength && index < iRightLength)
156                 {
157                     if (sLeft[index] < sRight[index])
158                         return -1;
159                     else if (sLeft[index] > sRight[index])
160                         return 1;
161                     else
162                         index++;
163                 }
164                 return iLeftLength - iRightLength;
165 
166             }
167         }
168         //Verify Signature
169         private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
170         {
171             string hash = "";
172             int ret = 0;
173             ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
174             if (ret != 0)
175                 return ret;
176             //System.Console.WriteLine(hash);
177             if (hash == sSigture)
178                 return 0;
179             else
180             {
181                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
182             }
183         }
184 
185         public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
186         {
187             ArrayList AL = new ArrayList();
188             AL.Add(sToken);
189             AL.Add(sTimeStamp);
190             AL.Add(sNonce);
191             AL.Add(sMsgEncrypt);
192             AL.Sort(new DictionarySort());
193             string raw = "";
194             for (int i = 0; i < AL.Count; ++i)
195             {
196                 raw += AL[i];
197             }
198 
199             SHA1 sha;
200             ASCIIEncoding enc;
201             string hash = "";
202             try
203             {
204                 sha = new SHA1CryptoServiceProvider();
205                 enc = new ASCIIEncoding();
206                 byte[] dataToHash = enc.GetBytes(raw);
207                 byte[] dataHashed = sha.ComputeHash(dataToHash);
208                 hash = BitConverter.ToString(dataHashed).Replace("-", "");
209                 hash = hash.ToLower();
210             }
211             catch (Exception)
212             {
213                 return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
214             }
215             sMsgSignature = hash;
216             return 0;
217         }
218     }
219 }
复制代码
复制代码
  1 using System;
  2 using System.IO;
  3 using System.Net;
  4 using System.Security.Cryptography;
  5 using System.Text;
  6 
  7 namespace  Core.Common.Wechat
  8 {
  9     /// <summary>
 10     /// 
 11     /// </summary>
 12     public class Cryptography
 13     {
 14         public static UInt32 HostToNetworkOrder(UInt32 inval)
 15         {
 16             UInt32 outval = 0;
 17             for (int i = 0; i < 4; i++)
 18                 outval = (outval << 8) + ((inval >> (i * 8)) & 255);
 19             return outval;
 20         }
 21 
 22         public static Int32 HostToNetworkOrder(Int32 inval)
 23         {
 24             Int32 outval = 0;
 25             for (int i = 0; i < 4; i++)
 26                 outval = (outval << 8) + ((inval >> (i * 8)) & 255);
 27             return outval;
 28         }
 29         /// <summary>
 30         /// 解密方法
 31         /// </summary>
 32         /// <param name="Input">密文</param>
 33         /// <param name="EncodingAESKey"></param>
 34         /// <returns></returns>
 35         /// 
 36         public static string AES_decrypt(String Input, string EncodingAESKey, ref string appid)
 37         {
 38             byte[] Key;
 39             Key = Convert.FromBase64String(EncodingAESKey + "=");
 40             byte[] Iv = new byte[16];
 41             Array.Copy(Key, Iv, 16);
 42             byte[] btmpMsg = AES_decrypt(Input, Iv, Key);
 43 
 44             int len = BitConverter.ToInt32(btmpMsg, 16);
 45             len = IPAddress.NetworkToHostOrder(len);
 46 
 47 
 48             byte[] bMsg = new byte[len];
 49             byte[] bAppid = new byte[btmpMsg.Length - 20 - len];
 50             Array.Copy(btmpMsg, 20, bMsg, 0, len);
 51             Array.Copy(btmpMsg, 20 + len, bAppid, 0, btmpMsg.Length - 20 - len);
 52             string oriMsg = Encoding.UTF8.GetString(bMsg);
 53             appid = Encoding.UTF8.GetString(bAppid);
 54 
 55 
 56             return oriMsg;
 57         }
 58 
 59         public static String AES_encrypt(String Input, string EncodingAESKey, string appid)
 60         {
 61             byte[] Key;
 62             Key = Convert.FromBase64String(EncodingAESKey + "=");
 63             byte[] Iv = new byte[16];
 64             Array.Copy(Key, Iv, 16);
 65             string Randcode = CreateRandCode(16);
 66             byte[] bRand = Encoding.UTF8.GetBytes(Randcode);
 67             byte[] bAppid = Encoding.UTF8.GetBytes(appid);
 68             byte[] btmpMsg = Encoding.UTF8.GetBytes(Input);
 69             byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));
 70             byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bAppid.Length + btmpMsg.Length];
 71 
 72             Array.Copy(bRand, bMsg, bRand.Length);
 73             Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length);
 74             Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length);
 75             Array.Copy(bAppid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bAppid.Length);
 76 
 77             return AES_encrypt(bMsg, Iv, Key);
 78 
 79         }
 80         private static string CreateRandCode(int codeLen)
 81         {
 82             string codeSerial = "2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z";
 83             if (codeLen == 0)
 84             {
 85                 codeLen = 16;
 86             }
 87             string[] arr = codeSerial.Split(',');
 88             string code = "";
 89             int randValue = -1;
 90             Random rand = new Random(unchecked((int)DateTime.Now.Ticks));
 91             for (int i = 0; i < codeLen; i++)
 92             {
 93                 randValue = rand.Next(0, arr.Length - 1);
 94                 code += arr[randValue];
 95             }
 96             return code;
 97         }
 98 
 99         private static String AES_encrypt(String Input, byte[] Iv, byte[] Key)
100         {
101             var aes = new RijndaelManaged();
102             //秘钥的大小,以位为单位
103             aes.KeySize = 256;
104             //支持的块大小
105             aes.BlockSize = 128;
106             //填充模式
107             aes.Padding = PaddingMode.PKCS7;
108             aes.Mode = CipherMode.CBC;
109             aes.Key = Key;
110             aes.IV = Iv;
111             var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
112             byte[] xBuff = null;
113 
114             using (var ms = new MemoryStream())
115             {
116                 using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
117                 {
118                     byte[] xXml = Encoding.UTF8.GetBytes(Input);
119                     cs.Write(xXml, 0, xXml.Length);
120                 }
121                 xBuff = ms.ToArray();
122             }
123             String Output = Convert.ToBase64String(xBuff);
124             return Output;
125         }
126 
127         private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key)
128         {
129             var aes = new RijndaelManaged();
130             //秘钥的大小,以位为单位
131             aes.KeySize = 256;
132             //支持的块大小
133             aes.BlockSize = 128;
134             //填充模式
135             //aes.Padding = PaddingMode.PKCS7;
136             aes.Padding = PaddingMode.None;
137             aes.Mode = CipherMode.CBC;
138             aes.Key = Key;
139             aes.IV = Iv;
140             var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
141             byte[] xBuff = null;
142 
143             #region 自己进行PKCS7补位,用系统自己带的不行
144             byte[] msg = new byte[Input.Length + 32 - Input.Length % 32];
145             Array.Copy(Input, msg, Input.Length);
146             byte[] pad = KCS7Encoder(Input.Length);
147             Array.Copy(pad, 0, msg, Input.Length, pad.Length);
148             #endregion
149 
150             #region 注释的也是一种方法,效果一样
151             //ICryptoTransform transform = aes.CreateEncryptor();
152             //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
153             #endregion
154 
155             using (var ms = new MemoryStream())
156             {
157                 using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
158                 {
159                     cs.Write(msg, 0, msg.Length);
160                 }
161                 xBuff = ms.ToArray();
162             }
163 
164             String Output = Convert.ToBase64String(xBuff);
165             return Output;
166         }
167 
168         private static byte[] KCS7Encoder(int text_length)
169         {
170             int block_size = 32;
171             // 计算需要填充的位数
172             int amount_to_pad = block_size - (text_length % block_size);
173             if (amount_to_pad == 0)
174             {
175                 amount_to_pad = block_size;
176             }
177             // 获得补位所用的字符
178             char pad_chr = chr(amount_to_pad);
179             string tmp = "";
180             for (int index = 0; index < amount_to_pad; index++)
181             {
182                 tmp += pad_chr;
183             }
184             return Encoding.UTF8.GetBytes(tmp);
185         }
186         /**
187          * 将数字转化成ASCII码对应的字符,用于对明文进行补码
188          * 
189          * @param a 需要转化的数字
190          * @return 转化得到的字符
191          */
192         static char chr(int a)
193         {
194 
195             byte target = (byte)(a & 0xFF);
196             return (char)target;
197         }
198         private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key)
199         {
200             RijndaelManaged aes = new RijndaelManaged();
201             aes.KeySize = 256;
202             aes.BlockSize = 128;
203             aes.Mode = CipherMode.CBC;
204             aes.Padding = PaddingMode.None;
205             aes.Key = Key;
206             aes.IV = Iv;
207             var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
208             byte[] xBuff = null;
209             using (var ms = new MemoryStream())
210             {
211                 using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
212                 {
213                     byte[] xXml = Convert.FromBase64String(Input);
214                     byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32];
215                     Array.Copy(xXml, msg, xXml.Length);
216                     cs.Write(xXml, 0, xXml.Length);
217                 }
218                 xBuff = decode2(ms.ToArray());
219             }
220             return xBuff;
221         }
222         private static byte[] decode2(byte[] decrypted)
223         {
224             int pad = (int)decrypted[decrypted.Length - 1];
225             if (pad < 1 || pad > 32)
226             {
227                 pad = 0;
228             }
229             byte[] res = new byte[decrypted.Length - pad];
230             Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad);
231             return res;
232         }
233     }
234 }
复制代码

复制代码
复制代码