1、进入微信公众号后台设置微信服务器配置参数(注意:Token和EncodingAESKey必须和微信服务器验证参数保持一致,不然验证不会通过)。
2、设置为安全模式
3、代码实现(主要分为验证接口和消息处理接口):
1 ///2 /// 验证接口 3 /// 4 /// 签名 5 /// 时间戳 6 /// 7 /// 8 ///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 /// 21 /// 接收消息并处理和返回相应结果22 /// 23 /// 当加密模式时才会有该变量(消息签名)24 /// 签名25 /// 时间戳26 /// 27 ///28 [HttpPost, Route("Message")]29 [AllowAnonymous]30 public ActionResult MessagePost(string msg_signature, string signature, string timestamp, string nonce)31 {32 try33 {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参数的timestamp104 // @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce105 // @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 try116 {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 = "";132 string MsgSigLabelHead = " ";134 string TimeStampLabelHead = " ";136 string NonceLabelHead = " ";138 sEncryptMsg = sEncryptMsg + " " + EncryptLabelHead + raw + EncryptLabelTail;139 sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;140 sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;141 sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;142 sEncryptMsg += " ";143 return 0;144 }145 146 public class DictionarySort : System.Collections.IComparer147 {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 else162 index++;163 }164 return iLeftLength - iRightLength;165 166 }167 }168 //Verify Signature169 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 else180 {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 try203 {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 ///10 /// 11 /// 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 ///30 /// 解密方法 31 /// 32 /// 密文 33 /// 34 ///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 #endregion149 150 #region 注释的也是一种方法,效果一样151 //ICryptoTransform transform = aes.CreateEncryptor();152 //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);153 #endregion154 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 }