• 【详细】.NET企业微信回调配置(数据回调URL和指令回调URL验证)


    前言:

      前段时间因为公司业务需求,需要将微信小程序与企业微信对接通,也就是把小程序绑定到对应的企业微信账号下,在该企业微信的用户可以将该小程序绑定到工作台中,然后可以在工作台中打开该小程序并授权。不过将微信小程序与企业微信对接通需要后台去做数据回调URL和指令回调URL验证,因为第一次接触这个然后企业微信文档写的也不是很详细,并且在全网没有找到一篇.NET相关企业微信回调配置验证有用的文章,所以这里把自己的配置详细过程分享出来,希望能够帮助更多的同学。

    企业微信回调配置相关文档

    回调配置:

    主要讲的是回调配置的一些验证流程和请求接口。

    https://work.weixin.qq.com/api/doc/90000/90135/90930

    C#解密类库:

    https://open.work.weixin.qq.com/wwopen/downloadfile/csharp.zip

    企业微信回调配置验证完整流程

    注意:配置回调服务时,需要能同时支持HttpGet以及HttpPost两种能力,注意接口一定要是https的安全域名地址。

    • HttpGet接口用于验证数据回调URL有效性
    • HttpPost接口用于验证指令回调URL有效性

    所以我们可以只定义一个接口,通过企业微信请求过来的类型进行不同回调URL的有效性验证。

    处理企业号的信息接口:

    public class EnterpriseWechatCallbackController : Controller
    {
    
        //企业微信后台开发者设置的token, corpID, EncodingAESKey
        private readonly string sToken = "追逐时光者";//企业微信后台,开发者设置的Token
        private readonly string sCorpID = "追逐时光者";//企业号corpid是企业号的专属编号(CorpID)[不同场景含义不同,详见文档说明(ToUserName:企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid)]
        private readonly string sEncodingAESKey = "追逐时光者";//企业微信后台,开发者设置的EncodingAESKey
    
    
        /// <summary>
        /// 处理企业号的信息
        /// get:数据回调URL验证;
        /// post:指令回调URL验证;
        /// </summary>
        public ActionResult EtWechatCommunication()
        {
            string httpMethod = Request.HttpMethod.ToUpper();
    
            if (httpMethod == "POST")
            {
                //获取请求中的xml数据
                string postString = GetXMLParameters(Request);
    
                string responseContent = "响应失败,未获取到xml中的请求参数";
                if (!string.IsNullOrEmpty(postString))
                {
                    //指令响应回调
                    responseContent = CommandCallback(Request, postString);
                }
    
                return Content(responseContent);
            }
            else
            {
                return EtWachatCheckVerifyURL();
            }
        }
    
    
        /// <summary>
        /// 数据回调URL验证
        /// </summary>
        /// <returns></returns>
        public ActionResult EtWachatCheckVerifyURL()
        {
            string signature = Request.QueryString["msg_signature"];//微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
            string timestamp = Request.QueryString["timestamp"];//时间戳
            string nonce = Request.QueryString["nonce"];//随机数
            string httpMethod = Request.HttpMethod.ToUpper();
    
            if (httpMethod == "GET")//验证回调URL(注意:企业回调的url-该url不做任何的业务逻辑,仅仅微信查看是否可以调通)
            {
                try
                {
                    //在1秒内响应GET请求,响应内容为上一步得到的明文消息内容decryptEchoString(不能加引号,不能带bom头,不能带换行符)
                    string echostr = Request.QueryString["echostr"];//加密的随机字符串,以msg_encrypt格式提供。需要解密并返回echostr明文,解密后有random、msg_len、msg、$CorpID四个字段,其中msg即为echostr明文
    
                    if (!IsNullOrWhiteSpace(signature) && !IsNullOrWhiteSpace(timestamp) && !IsNullOrWhiteSpace(nonce) && !IsNullOrWhiteSpace(echostr))
                    {
                        string decryptEchoString = null;
                        if (CheckVerifyURL(sToken, signature, timestamp, nonce, sCorpID, sEncodingAESKey, echostr, ref decryptEchoString))
                        {
                            if (!string.IsNullOrEmpty(decryptEchoString))
                            {
                                //必须要返回解密之后的明文
                                return Content(decryptEchoString);
                            }
                        }
                    }
                    else
                    {
                        return Content("fail");
                    }
                }
                catch (Exception ex)
                {
                    return Content("fail");
                }
            }
    
            return Content("fail");
        }
    
        /// <summary>
        /// 验证URL有效性
        /// </summary>
        /// <param name="token">企业微信后台,开发者设置的Token</param>
        /// <param name="signature">签名串,对应URL参数的msg_signature</param>
        /// <param name="timestamp">时间戳</param>
        /// <param name="nonce">随机数</param>
        /// <param name="corpId">ToUserName为企业号的CorpID</param>
        /// <param name="encodingAESKey">企业微信后台,开发者设置的EncodingAESKey</param>
        /// <param name="echostr">随机串,对应URL参数的echostr</param>
        /// <param name="retEchostr">解密之后的echostr,当return返回0时有效</param>
        /// <returns></returns>
        private bool CheckVerifyURL(string token, string signature, string timestamp, string nonce, string corpId, string encodingAESKey, string echostr, ref string retEchostr)
        {
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId);
    
            int result = wxcpt.VerifyURL(signature, timestamp, nonce, echostr, ref retEchostr);
    
            if (result != 0)
            {
                return false;//FAIL
            }
    
            //result==0表示验证成功、retEchostr参数表示明文
            //用户需要将retEchostr作为get请求的返回参数、返回给企业微信号
            return true;
        }
    
        /// <summary>
        /// 指令响应回调
        /// </summary>
        /// <param name="Request"></param>
        /// <param name="postString">post请求的xml参数</param>
        /// <returns></returns>
        private string CommandCallback(HttpRequestBase Request, string postString)
        {
            string signature = Request.QueryString["msg_signature"];//微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
            string timestamp = Request.QueryString["timestamp"];//时间戳
            string nonce = Request.QueryString["nonce"];//随机数                             
            var xmlDoc = XDocument.Parse(postString);//xml数据转化
    
            try
            {
                //https://work.weixin.qq.com/api/doc/90001/90143/90613
                //在发生授权、通讯录变更、ticket变化等事件时,企业微信服务器会向应用的“指令回调URL”推送相应的事件消息。
                //消息结构体将使用创建应用时的EncodingAESKey进行加密(特别注意, 在第三方回调事件中使用加解密算法,receiveid的内容为suiteid),请参考接收消息解析数据包。 本章节的回调事件,服务商在收到推送后都必须直接返回字符串 “success”,若返回值不是 “success”,企业微信会把返回内容当作错误信息。
                if (xmlDoc.Root.Element("Encrypt") != null)
                {
                    //将post请求的数据进行xml解析,并将<Encrypt> 标签的内容进行解密,解密出来的明文即是用户回复消息的明文
                    //接收并读取POST过来的XML文件流
                    string decryptionParame = null;  // 解析之后的明文
    
                    // 注意注意:sCorpID
                    // @param sReceiveId: 不同场景含义不同,详见文档说明([消息加密时为 CorpId]ToUserName:企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid)
    
                    WXBizMsgCrypt crypt = new WXBizMsgCrypt(sToken, sEncodingAESKey, xmlDoc.Root.Element("ToUserName").Value);
                    var result = crypt.DecryptMsg(signature, timestamp, nonce, postString, ref decryptionParame);
    
                    if (result != 0)
                    {
                        return "fial";
                    }
    
                    //响应应答处理
                    return new InstructionCallbackResponse().ReceiveResponse(decryptionParame, timestamp, signature,sToken, sEncodingAESKey, sCorpID);
                }
            }
            catch (Exception ex)
            {
                //LoggerHelper._.Debug("异常:" + ex.Message);
            }
    
            return "fail";
        }
    
    
        /// <summary>
        /// 验证是否为空
        /// </summary>
        /// <param name="strParame">验证参数</param>
        /// <returns></returns>
        private bool IsNullOrWhiteSpace(string strParame)
        {
            if (string.IsNullOrWhiteSpace(strParame))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    
        /// <summary>
        /// 获取post请求中的xml参数
        /// </summary>
        /// <returns></returns>
        private string GetXMLParameters(HttpRequestBase Request)
        {
            string replyMsg;
            using (Stream stream = Request.InputStream)
            {
                Byte[] postBytes = new Byte[stream.Length];
                stream.Read(postBytes, 0, (Int32)stream.Length);
                replyMsg = Encoding.UTF8.GetString(postBytes);
            }
            return replyMsg;
        }
    
    }

    指令回调响应应答处理:

        /// <summary>
        /// 指令回调响应应答处理
        /// </summary>
        public class InstructionCallbackResponse
        {
            /// <summary>
            /// 响应应答处理
            /// </summary>
            /// <param name="sMsg">解密参数</param>
            /// <param name="timestamp">时间戳</param>
            /// <param name="signature">签名</param>
            /// <param name="sToken">企业微信后台,开发者设置的Token</param>
            /// <param name="sEncodingAESKey">开发者设置的EncodingAESKey</param>
            /// <param name="sCorpID">业号corpid是企业号的专属编号(CorpID)</param>
            /// <returns></returns>
            public string ReceiveResponse(string sMsg, string timestamp, string signature, string sToken, string sEncodingAESKey, string sCorpID)
            {
                string responseMessage = "success";//响应内容   
                var xmlDoc = XDocument.Parse(sMsg);//xml数据转化
    
                //区分普通消息与第三方应用授权推送消息,MsgType有值说明是普通消息,反之则是第三方应用授权推送消息
                if (xmlDoc.Root.Element("MsgType") != null)
                {
                    var msgType = (ResponseMsgType)Enum.Parse(typeof(ResponseMsgType), xmlDoc.Root.Element("MsgType").Value, true);
                    switch (msgType)
                    {
                        case ResponseMsgType.Text://文本消息
                            responseMessage = ResponseMessageText(xmlDoc, timestamp, signature,sToken,sEncodingAESKey,sCorpID);
                            break;
                        case ResponseMsgType.Image:
                            responseMessage = ResponseMessageImage();
                            break;
                        case ResponseMsgType.Voice:
                            responseMessage = ResponseMessageVoice();
                            break;
                        case ResponseMsgType.Video:
                            responseMessage = ResponseMessageVideo();
                            break;
                        case ResponseMsgType.News:
                            responseMessage = ResponseMessageNews();
                            break;
                    }
                }
                else if (xmlDoc.Root.Element("InfoType") != null)
                {
                    //第三方回调
                    var infoType = (ResponseInfoType)Enum.Parse(typeof(ResponseInfoType), xmlDoc.Root.Element("InfoType").Value, true);
    
                    switch (infoType)
                    {
                        case ResponseInfoType.suite_ticket:
                            {
                                //LoggerHelper._.Warn("suite_ticket===>>>>>,进来了,获取到的SuiteTicket票据为" + xmlDoc.Root.Element("SuiteTicket").Value);
                            }
                            break;
                    }
                }
                else
                {
                    //其他情况
                }
    
                // result==0表示解密成功,sMsg表示解密之后的明文xml串
                //服务器未正确返回响应字符串 “success”
                return responseMessage;
            }
    
    
            #region 相关事件实现
    
            /// <summary>
            /// 消息文本回复
            /// </summary>
            /// <returns></returns>
            public string ResponseMessageText(XDocument xmlDoc, string timestamp, string nonce,string sToken,string sEncodingAESKey,string sCorpID)
            {
                string FromUserName = xmlDoc.Root.Element("FromUserName").Value;
                string ToUserName = xmlDoc.Root.Element("ToUserName").Value;
                string Content = xmlDoc.Root.Element("Content").Value;
    
                string xml = "<xml>";
                xml += "<ToUserName><![CDATA[" + ToUserName + "]]></ToUserName>";
                xml += "<FromUserName><![CDATA[" + FromUserName + "]]></FromUserName>";
                xml += "<CreateTime>" + GetCurrentTimeUnix() + "</CreateTime>";
                xml += "<MsgType><![CDATA[text]]></MsgType>";
                xml += "<Content><![CDATA[" + Content + "]]></Content>";
                xml += "</xml>";
                //"" + Content + "0";//回复内容 FuncFlag设置为1的时候,自动星标刚才接收到的消息,适合活动统计使用
                WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
                string sEncryptMsg = "";// 加密后的可以直接回复用户的密文;
                wxcpt.EncryptMsg(xml, timestamp, nonce, ref sEncryptMsg);
    
                //返回
                return sEncryptMsg;
            }
    
            /// <summary>
            /// 图片消息
            /// </summary>
            /// <returns></returns>
    
            public string ResponseMessageImage()
            {
                return "success";
            }
    
            /// <summary>
            /// 语音消息
            /// </summary>
            /// <returns></returns>
            public string ResponseMessageVoice()
            {
                return "success";
            }
    
            /// <summary>
            /// 视频消息
            /// </summary>
            /// <returns></returns>
            public string ResponseMessageVideo()
            {
                return "success";
            }
    
            /// <summary>
            /// 图文消息
            /// </summary>
            /// <returns></returns>
            public string ResponseMessageNews()
            {
                return "success";
            }
    
            #endregion
    
            /// <summary>
            /// 获取当前时间戳
            /// </summary>
            /// <returns></returns>
            public static string GetCurrentTimeUnix()
            {
                TimeSpan cha = (DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)));
                long t = (long)cha.TotalSeconds;
                return t.ToString();
            }
        }

    消息类型枚举(enumType):

    第三方应用授权推送消息类型(ResponseInfoType)

    /// <summary>
        /// 第三方应用授权推送消息类型
        /// </summary>
        public enum ResponseInfoType
        {
            /// <summary>
            /// 推送suite_ticket 企业微信服务器会定时(每十分钟)推送ticket。ticket会实时变更,并用于后续接口的调用。
            /// </summary>
            suite_ticket =1,
    
            /// <summary>
            /// 授权成功通知
            /// </summary>
            create_auth = 2,
    
            /// <summary>
            /// 成员通知事件
            /// </summary>
            change_contact = 3
        }

    普通消息响应类型(ResponseMsgType)

    /// <summary>
        /// 普通消息响应类型
        /// </summary>
        public enum ResponseMsgType
        {
            /// <summary>
            /// 文本消息
            /// </summary>
            Text = 0,
            /// <summary>
            /// 图文消息
            /// </summary>
            News = 1,
            /// <summary>
            /// 图片消息
            /// </summary>
            Image = 3,
            /// <summary>
            /// 语音消息
            /// </summary>
            Voice = 4,
            /// <summary>
            /// 视频消息
            /// </summary>
            Video = 5
        }

    C#解密类库

    WXBizMsgCrypt

     public class WXBizMsgCrypt
        {
            string m_sToken;
            string m_sEncodingAESKey;
            string m_sReceiveId;
    
            //-40001 : 签名验证错误
            //-40002 :  xml解析失败
            //-40003 :  sha加密生成签名失败
            //-40004 :  AESKey 非法
            //-40005 :  corpid 校验错误
            //-40006 :  AES 加密失败
            //-40007 : AES 解密失败
            //-40008 : 解密后得到的buffer非法
            //-40009 :  base64加密异常
            //-40010 :  base64解密异常
    
            enum WXBizMsgCryptErrorCode
            {
                WXBizMsgCrypt_OK = 0,
                WXBizMsgCrypt_ValidateSignature_Error = -40001,
                WXBizMsgCrypt_ParseXml_Error = -40002,
                WXBizMsgCrypt_ComputeSignature_Error = -40003,
                WXBizMsgCrypt_IllegalAesKey = -40004,
                WXBizMsgCrypt_ValidateCorpid_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 sReceiveId: 不同场景含义不同,详见文档说明([消息加密时为 CorpId]ToUserName:企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid)
            public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sReceiveId)
            {
                m_sToken = sToken;
                m_sReceiveId = sReceiveId;
                m_sEncodingAESKey = sEncodingAESKey;
            }
    
            //验证URL
            // @param sMsgSignature: 签名串,对应URL参数的msg_signature
            // @param sTimeStamp: 时间戳,对应URL参数的timestamp
            // @param sNonce: 随机串,对应URL参数的nonce
            // @param sEchoStr: 随机串,对应URL参数的echostr
            // @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
            // @return:成功0,失败返回对应的错误码
            public int VerifyURL(string sMsgSignature, string sTimeStamp, string sNonce, string sEchoStr, ref string sReplyEchoStr)
            {
                int ret = 0;
                if (m_sEncodingAESKey.Length != 43)
                {
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
                }
                ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEchoStr, sMsgSignature);
                if (0 != ret)
                {
                    return ret;
                }
                sReplyEchoStr = "";
                string cpid = "";
                try
                {
                    sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid); //m_sReceiveId);
                }
                catch (Exception)
                {
                    sReplyEchoStr = "";
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
                }
                if (cpid != m_sReceiveId)
                {
                    sReplyEchoStr = "";
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
                }
                return 0;
            }
    
            // 检验消息的真实性,并且获取解密后的明文
            // @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;
    
                    LoggerHelper._.Info("进来解密了,解密密文sEncryptMsg为:" + sEncryptMsg);
                }
                catch (Exception)
                {
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
                }
                //verify signature
                int ret = 0;
                ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
    
                LoggerHelper._.Info("进来解密了,解密结果:" + ret);
    
                if (ret != 0)
                    return ret;
                //decrypt
                string cpid = "";
                try
                {
                    sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
                }
                catch (FormatException)
                {
                    sMsg = "";
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
                }
                catch (Exception)
                {
                    sMsg = "";
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
                }
                if (cpid != m_sReceiveId)
                    return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_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_sReceiveId);
                }
                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;
                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)
            {
                //校验规则:https://work.weixin.qq.com/api/doc#90000/90139/90968/%E6%B6%88%E6%81%AF%E4%BD%93%E7%AD%BE%E5%90%8D%E6%A0%A1%E9%AA%8C
    
                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;
            }
    
        }

    Cryptography

        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 corpid)
            {
                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[] bCorpid = new byte[btmpMsg.Length - 20 - len];
                Array.Copy(btmpMsg, 20, bMsg, 0, len);
                Array.Copy(btmpMsg, 20 + len, bCorpid, 0, btmpMsg.Length - 20 - len);
                string oriMsg = Encoding.UTF8.GetString(bMsg);
                corpid = Encoding.UTF8.GetString(bCorpid);
    
    
                return oriMsg;
            }
    
            public static String AES_encrypt(String Input, string EncodingAESKey, string corpid)
            {
                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[] bCorpid = Encoding.UTF8.GetBytes(corpid);
                byte[] btmpMsg = Encoding.UTF8.GetBytes(Input);
                byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));
                byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bCorpid.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(bCorpid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bCorpid.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();
    #if NET45
                var aes = new RijndaelManaged();
    #else
                var aes = Aes.Create();
    #endif
                //秘钥的大小,以位为单位
                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();
    #if NET45
                var aes = new RijndaelManaged();
    #else
                var aes = Aes.Create();
    #endif
                //秘钥的大小,以位为单位
                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();
    #if NET45
                var aes = new RijndaelManaged();
    #else
                var aes = Aes.Create();
    #endif
                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;
            }
        }

    企业微信后台验证回调URL有效性 

     

    作者:追逐时光者

    作者简介:一个热爱编程,善于分享,喜欢学习、探索、尝试新事物,新技术的程序猿。

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得时光这篇文章有帮助的话,可以点一下右下角的【♥推荐♥】,希望能够持续的为大家带来好的技术文章,文中可能存在描述不正确或错误的地方,欢迎指正、补充,不胜感激 !

  • 相关阅读:
    基于Ubuntu Jeos打造自己的精简版Linux服务器
    35 vs 53怎么裁
    父母在,不远游
    linux deepin是基于linux mint修改
    novell
    Sahi
    virtualbox on windows store vdi on ndfs due the file will bigger than 4gb
    在Linux下配置邮件系统
    CSS3 backgroundsize 属性
    dede:list及dede:arclist 按权重排序的方法
  • 原文地址:https://www.cnblogs.com/Can-daydayup/p/15228111.html
Copyright © 2020-2023  润新知