Home > Article > WeChat Applet > Message body signature, encryption and decryption developed by WeChat
The first few articles are mainly preparations for WeChat development, and there is no technical content. In the first and second articles, I mainly talked about using Peanut Shell to cooperate with VS for code debugging. I was once complained by garden friends that I was a joke invited by Peanut Shell. I had no choice but to distinguish myself from Peanut Shell. Boundaries, before entering the main text of this article, I would like to introduce ngrok, a tool that is more useful than peanut shells. I will not explain the benefits of ngrok in detail here. After all, it is not the focus of this article
For safety reasons, The WeChat public platform added the message body encryption and decryption function in October. First, the signature needs to be verified first, which is used by the public platform and public accounts to verify the correctness of the message body. Secondly, for ordinary messages pushed to public accounts and Event Messages and devices pushed to the device's public account are encrypted. Finally, the public account's reply to the ciphertext message also needs to be encrypted. After the encryption and decryption function is enabled, when the official platform server pushes a message to the configured address of the official account server, the URL will add and add two parameters, one is the encryption type and the other is the message body signature, and this is reflected new function. The encryption algorithm uses AES. For instructions on plaintext mode, compatibility mode, and security mode, please refer to the official documentation.
The official demo is provided to help verify message authenticity and encryption and decryption, and again I will not go into details. After downloading, you can call it directly. Please see the code below:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Security.Cryptography;using System.IO;using System.Net;namespace WxApi { public class Cryptography { public static UInt32 HostToNetworkOrder(UInt32 inval) { UInt32 outval = 0; for (int i = 0; i < 4; i++) outval = (outval << 8) + ((inval >> (i * 8)) & 255); return outval; } public static Int32 HostToNetworkOrder(Int32 inval) { Int32 outval = 0; for (int i = 0; i < 4; i++) outval = (outval << 8) + ((inval >> (i * 8)) & 255); return outval; } /// <summary> /// 解密方法 /// </summary> /// <param name="Input">密文</param> /// <param name="EncodingAESKey"></param> /// <returns></returns> /// public static string AES_decrypt(String Input, string EncodingAESKey, ref string appid) { byte[] Key; Key = Convert.FromBase64String(EncodingAESKey + "="); byte[] Iv = new byte[16]; Array.Copy(Key, Iv, 16); byte[] btmpMsg = AES_decrypt(Input, Iv, Key); int len = BitConverter.ToInt32(btmpMsg, 16); len = IPAddress.NetworkToHostOrder(len); byte[] bMsg = new byte[len]; byte[] bAppid = new byte[btmpMsg.Length - 20 - len]; Array.Copy(btmpMsg, 20, bMsg, 0, len); Array.Copy(btmpMsg, 20 + len, bAppid, 0, btmpMsg.Length - 20 - len); string oriMsg = Encoding.UTF8.GetString(bMsg); appid = Encoding.UTF8.GetString(bAppid); return oriMsg; } public static String AES_encrypt(String Input, string EncodingAESKey, string appid) { byte[] Key; Key = Convert.FromBase64String(EncodingAESKey + "="); byte[] Iv = new byte[16]; Array.Copy(Key, Iv, 16); string Randcode = CreateRandCode(16); byte[] bRand = Encoding.UTF8.GetBytes(Randcode); byte[] bAppid = Encoding.UTF8.GetBytes(appid); byte[] btmpMsg = Encoding.UTF8.GetBytes(Input); byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length)); byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bAppid.Length + btmpMsg.Length]; Array.Copy(bRand, bMsg, bRand.Length); Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length); Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length); Array.Copy(bAppid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bAppid.Length); return AES_encrypt(bMsg, Iv, Key); } private static string CreateRandCode(int codeLen) { 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"; if (codeLen == 0) { codeLen = 16; } string[] arr = codeSerial.Split(','); string code = ""; int randValue = -1; Random rand = new Random(unchecked((int)DateTime.Now.Ticks)); for (int i = 0; i < codeLen; i++) { randValue = rand.Next(0, arr.Length - 1); code += arr[randValue]; } return code; } private static String AES_encrypt(String Input, byte[] Iv, byte[] Key) { var aes = new RijndaelManaged(); //秘钥的大小,以位为单位 aes.KeySize = 256; //支持的块大小 aes.BlockSize = 128; //填充模式 aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.CBC; aes.Key = Key; aes.IV = Iv; var encrypt = aes.CreateEncryptor(aes.Key, aes.IV); byte[] xBuff = null; using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write)) { byte[] xXml = Encoding.UTF8.GetBytes(Input); cs.Write(xXml, 0, xXml.Length); } xBuff = ms.ToArray(); } String Output = Convert.ToBase64String(xBuff); return Output; } private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key) { var aes = new RijndaelManaged(); //秘钥的大小,以位为单位 aes.KeySize = 256; //支持的块大小 aes.BlockSize = 128; //填充模式 //aes.Padding = PaddingMode.PKCS7; aes.Padding = PaddingMode.None; aes.Mode = CipherMode.CBC; aes.Key = Key; aes.IV = Iv; var encrypt = aes.CreateEncryptor(aes.Key, aes.IV); byte[] xBuff = null; #region 自己进行PKCS7补位,用系统自己带的不行 byte[] msg = new byte[Input.Length + 32 - Input.Length % 32]; Array.Copy(Input, msg, Input.Length); byte[] pad = KCS7Encoder(Input.Length); Array.Copy(pad, 0, msg, Input.Length, pad.Length); #endregion #region 注释的也是一种方法,效果一样 //ICryptoTransform transform = aes.CreateEncryptor(); //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length); #endregion using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write)) { cs.Write(msg, 0, msg.Length); } xBuff = ms.ToArray(); } String Output = Convert.ToBase64String(xBuff); return Output; } private static byte[] KCS7Encoder(int text_length) { int block_size = 32; // 计算需要填充的位数 int amount_to_pad = block_size - (text_length % block_size); if (amount_to_pad == 0) { amount_to_pad = block_size; } // 获得补位所用的字符 char pad_chr = chr(amount_to_pad); string tmp = ""; for (int index = 0; index < amount_to_pad; index++) { tmp += pad_chr; } return Encoding.UTF8.GetBytes(tmp); } /** * 将数字转化成ASCII码对应的字符,用于对明文进行补码 * * @param a 需要转化的数字 * @return 转化得到的字符 */ static char chr(int a) { byte target = (byte)(a & 0xFF); return (char)target; } private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key) { RijndaelManaged aes = new RijndaelManaged(); aes.KeySize = 256; aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; aes.Key = Key; aes.IV = Iv; var decrypt = aes.CreateDecryptor(aes.Key, aes.IV); byte[] xBuff = null; using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write)) { byte[] xXml = Convert.FromBase64String(Input); byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32]; Array.Copy(xXml, msg, xXml.Length); cs.Write(xXml, 0, xXml.Length); } xBuff = decode2(ms.ToArray()); } return xBuff; } private static byte[] decode2(byte[] decrypted) { int pad = (int)decrypted[decrypted.Length - 1]; if (pad < 1 || pad > 32) { pad = 0; } byte[] res = new byte[decrypted.Length - pad]; Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad); return res; } } }
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Xml;using System.Collections;//using System.Web;using System.Security.Cryptography;//-40001 : 签名验证错误//-40002 : xml解析失败//-40003 : sha加密生成签名失败//-40004 : AESKey 非法//-40005 : appid 校验错误//-40006 : AES 加密失败//-40007 : AES 解密失败//-40008 : 解密后得到的buffer非法//-40009 : base64加密异常//-40010 : base64解密异常namespace WxApi { public class MsgCrypt { string m_sToken; string m_sEncodingAESKey; string m_sAppID; enum WXBizMsgCryptErrorCode { WXBizMsgCrypt_OK = 0, WXBizMsgCrypt_ValidateSignature_Error = -40001, WXBizMsgCrypt_ParseXml_Error = -40002, WXBizMsgCrypt_ComputeSignature_Error = -40003, WXBizMsgCrypt_IllegalAesKey = -40004, WXBizMsgCrypt_ValidateAppid_Error = -40005, WXBizMsgCrypt_EncryptAES_Error = -40006, WXBizMsgCrypt_DecryptAES_Error = -40007, WXBizMsgCrypt_IllegalBuffer = -40008, WXBizMsgCrypt_EncodeBase64_Error = -40009, WXBizMsgCrypt_DecodeBase64_Error = -40010 }; //构造函数 // @param sToken: 公众平台上,开发者设置的Token // @param sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey // @param sAppID: 公众帐号的appid public MsgCrypt(string sToken, string sEncodingAESKey, string sAppID) { m_sToken = sToken; m_sAppID = sAppID; m_sEncodingAESKey = sEncodingAESKey; } // 检验消息的真实性,并且获取解密后的明文 // @param sMsgSignature: 签名串,对应URL参数的msg_signature // @param sTimeStamp: 时间戳,对应URL参数的timestamp // @param sNonce: 随机串,对应URL参数的nonce // @param sPostData: 密文,对应POST请求的数据 // @param sMsg: 解密后的原文,当return返回0时有效 // @return: 成功0,失败返回对应的错误码 public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg) { if (m_sEncodingAESKey.Length != 43) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey; } XmlDocument doc = new XmlDocument(); XmlNode root; string sEncryptMsg; try { doc.LoadXml(sPostData); root = doc.FirstChild; sEncryptMsg = root["Encrypt"].InnerText; } catch (Exception) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error; } //verify signature int ret = 0; ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature); if (ret != 0) return ret; //decrypt string cpid = ""; try { sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid); } catch (FormatException) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error; } catch (Exception) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error; } if (cpid != m_sAppID) return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error; return 0; } //将企业号回复用户的消息加密打包 // @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串 // @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp // @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce // @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, // 当return返回0时有效 // return:成功0,失败返回对应的错误码 public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg) { if (m_sEncodingAESKey.Length != 43) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey; } string raw = ""; try { raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID); } catch (Exception) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error; } string MsgSigature = ""; int ret = 0; ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature); if (0 != ret) return ret; sEncryptMsg = ""; string EncryptLabelHead = "<Encrypt><![CDATA["; string EncryptLabelTail = "]]></Encrypt>"; string MsgSigLabelHead = "<MsgSignature><![CDATA["; string MsgSigLabelTail = "]]></MsgSignature>"; string TimeStampLabelHead = "<TimeStamp><![CDATA["; string TimeStampLabelTail = "]]></TimeStamp>"; string NonceLabelHead = "<Nonce><![CDATA["; string NonceLabelTail = "]]></Nonce>"; sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail; sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail; sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail; sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail; sEncryptMsg += "</xml>"; return 0; } public class DictionarySort : System.Collections.IComparer { public int Compare(object oLeft, object oRight) { string sLeft = oLeft as string; string sRight = oRight as string; int iLeftLength = sLeft.Length; int iRightLength = sRight.Length; int index = 0; while (index < iLeftLength && index < iRightLength) { if (sLeft[index] < sRight[index]) return -1; else if (sLeft[index] > sRight[index]) return 1; else index++; } return iLeftLength - iRightLength; } } //Verify Signature private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture) { string hash = ""; int ret = 0; ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash); if (ret != 0) return ret; //System.Console.WriteLine(hash); if (hash == sSigture) return 0; else { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error; } } public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature) { ArrayList AL = new ArrayList(); AL.Add(sToken); AL.Add(sTimeStamp); AL.Add(sNonce); AL.Add(sMsgEncrypt); AL.Sort(new DictionarySort()); string raw = ""; for (int i = 0; i < AL.Count; ++i) { raw += AL[i]; } SHA1 sha; ASCIIEncoding enc; string hash = ""; try { sha = new SHA1CryptoServiceProvider(); enc = new ASCIIEncoding(); byte[] dataToHash = enc.GetBytes(raw); byte[] dataHashed = sha.ComputeHash(dataToHash); hash = BitConverter.ToString(dataHashed).Replace("-", ""); hash = hash.ToLower(); } catch (Exception) { return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error; } sMsgSignature = hash; return 0; } } }
In the processing program, first obtain the data sent by the public platform server and convert it into a string. The code is as follows
string postStr = ""; Stream s = VqiRequest.GetInputStream();//此方法是对System.Web.HttpContext.Current.Request.InputStream的封装,可直接代码 byte[] b = new byte[s.Length]; s.Read(b, 0, (int)s.Length); postStr = Encoding.UTF8.GetString(b);
Then get the parameters in the url respectively: timestamp, nonce, msg_signature, encrypt_type. As you can see, there is no encrypt_type parameter in plaintext mode. As shown in the picture:
Clear text mode
Compatibility mode and security mode
Compatibility mode and security mode add two parameters: the signature and encryption type of the message body.
Since the compatibility mode is unlikely to be used in actual operations, we will not introduce it in detail here.
Continue with the above. After obtaining the parameters in the url, determine whether the value of encrypt_type is aes. If so, it means that the compatibility mode or safe mode is used. At this time, you need to call the decryption-related method for decryption.
if (encrypt_type == "aes") { requestXML.IsAes = true; requestXML.EncodingAESKey = aeskey; requestXML.token = token; requestXML.appid = appid; var ret = new MsgCrypt(token, aeskey, appid); int r = ret.DecryptMsg(msg_signature, timestamp, nonce, postStr, ref data); if (r!=0) { WxApi.Base.WriteBug("消息解密失败"); return null; } }
Otherwise, the received xml string will be parsed directly.
The picture below is the received ciphertext:
The decrypted content is as follows:
This xml can be parsed at this time.
When you need to reply to an encrypted request, the content of the reply also needs to be encrypted, so you need to determine whether the received message is encrypted before replying. If it is encrypted, you need to encrypt the content of the reply. and then reply. The method of replying to messages will be explained in detail in the next article. This article only explains the encryption process.
The processing code is as follows:
private static void Response(WeiXinRequest requestXML, string data) { if (requestXML.IsAes) { var wxcpt = new MsgCrypt(requestXML.token, requestXML.EncodingAESKey, requestXML.appid); wxcpt.EncryptMsg(data, Utils.ConvertDateTimeInt(DateTime.Now).ToString(), Utils.GetRamCode(), ref data); } Utils.ResponseWrite(data); }
Pass the received message entity and the content xml that needs to be replied. If it is encrypted, then respond after encryption, otherwise respond directly.
【Related recommendations】
1.WeChat public account platform source code download
2.Weizhichuang T+ WeChat robot Source code
The above is the detailed content of Message body signature, encryption and decryption developed by WeChat. For more information, please follow other related articles on the PHP Chinese website!