微信公众号开发--用.Net Core实现微信消息加解密
程序员文章站
2022-04-18 22:33:23
1、进入微信公众号后台设置微信服务器配置参数(注意:Token和EncodingAESKey必须和微信服务器验证参数保持一致,不然验证不会通过)。 2、设置为安全模式 3、代码实现(主要分为验证接口和消息处理接口): 加解密实现(微信公众号官网有源码) ......
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 }