Maison  >  Article  >  Applet WeChat  >  Signature du corps du message, cryptage et déchiffrement développés par WeChat

Signature du corps du message, cryptage et déchiffrement développés par WeChat

Y2J
Y2Joriginal
2017-05-09 09:33:502751parcourir

Les premiers articles sont principalement des préparatifs pour le développement de WeChat, et il n'y a aucun contenu technique. Dans les premier et deuxième articles, j'ai principalement parlé de l'utilisation de Peanut Shell pour coopérer avec VS pour le débogage de code. Des amis du jardin m'ont un jour plaint que j'étais une blague invitée par Peanut Shell et que je n'avais d'autre choix que de me distinguer de Peanut Shell. .Boundaries, avant d'entrer dans le texte de cet article, je voudrais présenter ngrok, un outil qui est plus utile que les coquilles de cacahuètes. Je n'expliquerai pas ici les avantages de ngrok. article

Pour des raisons de sécurité, la plateforme publique WeChat a ajouté la fonction de cryptage et de déchiffrement du corps du message en octobre. Tout d'abord, la signature doit d'abord être vérifiée, qui est utilisée par la plateforme publique et les comptes publics pour vérifier l'exactitude. du corps du message. Deuxièmement, pour les messages ordinaires poussés vers des comptes publics et les messages Événement et les appareils poussés vers le compte public de l'appareil sont cryptés. Enfin, la réponse du compte public au message chiffré doit également être cryptée. . Une fois la fonction de cryptage et de décryptage activée, lorsque le serveur de la plateforme officielle envoie un message à l'adresse configurée du serveur de compte officiel, l'URL sera ajoutée et ajoutera deux paramètres, l'un est le type de cryptage et l'autre est la signature du corps du message, et cela se reflète dans les nouvelles fonctionnalités. L'algorithme de cryptage utilise AES. Pour obtenir des instructions sur le mode texte brut, le mode de compatibilité et le mode de sécurité, veuillez vous référer à la documentation officielle

La démo officielle est fournie pour les classes d'assistance permettant de vérifier l'authenticité, le cryptage et le déchiffrement des messages, et. encore une fois, je n'entrerai pas dans les détails. Après le téléchargement, vous pouvez l'appeler directement. Veuillez consulter le code ci-dessous :

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(&#39;,&#39;);            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;
        }
    }
}

Dans le programme de traitement, récupérez d'abord les données envoyées par le serveur de la plateforme publique. et convertissez-le en chaîne. Le code est le suivant

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);

puis obtenez respectivement les paramètres dans l'url : timestamp, nonce, msg_signature, encrypt_type. Comme vous pouvez le voir, il n'y a pas de paramètre encrypt_type en mode texte brut. Comme le montre l'Signature du corps du message, cryptage et déchiffrement développés par WeChat :

Signature du corps du message, cryptage et déchiffrement développés par WeChat

Mode texte clair

Signature du corps du message, cryptage et déchiffrement développés par WeChat

Mode de compatibilité et mode sans échec

Le mode de compatibilité et le mode de sécurité ajoutent deux paramètres : la signature et le type de cryptage du corps du message.

Comme il est peu probable que le mode de compatibilité soit utilisé dans des opérations réelles, nous ne le présenterons pas en détail ici.

Continuez avec ce qui précède. Après avoir obtenu les paramètres dans l'URL, déterminez si la valeur de encrypt_type est aes. Si tel est le cas, cela signifie que le mode de compatibilité ou le mode sans échec est utilisé. À ce stade, vous devez appeler la méthode liée au décryptage pour le décryptage.

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;
               
                }
            }

Sinon, la chaîne XML reçue sera analysée directement.

L'Signature du corps du message, cryptage et déchiffrement développés par WeChat ci-dessous est le texte chiffré reçu :

Signature du corps du message, cryptage et déchiffrement développés par WeChat

Le contenu déchiffré est le suivant :

Signature du corps du message, cryptage et déchiffrement développés par WeChat

Ce XML peut être analysé à ce moment.

Lorsque vous devez répondre à une demande cryptée, le contenu de la réponse doit également être crypté, vous devez donc déterminer si le message reçu est crypté avant de répondre. S'il est crypté, vous devez le crypter. le contenu de la réponse, puis répondez. La méthode de réponse aux messages sera expliquée en détail dans le prochain article. Cet article explique uniquement le processus de cryptage.

Le code de traitement est le suivant :

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);
      
        }

Transmettez l'entité du message reçu et le contenu XML auquel il faut répondre S'il est crypté, répondez après le cryptage, sinon répondez directement. .

[Recommandations associées]

1.Téléchargement du code source de la plateforme de compte public WeChat

2.Weizhichuang T+ WeChat robot Source code

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn