.NET 微信小程序用户数据的签名验证和解密

微信小程序时下大热,抱着学习的心态了解了一下,目前没有搜到完整的.NET用户数据签名验证和解密代码,于是就写了一点。

简单使用方法:
1、客户端调用wx.getUserInfo方法,服务端创建WeChatLoginInfo类的实例接收客户端发来的数据;
2、服务端新建WeChatAppDecrypt类的实例,初始化此类时需传入appId与AppSecret用于验证;
3、调用WeChatAppDecrypt类中的Decrypt方法,传入步骤1中获取的WechatLoginInfo实例;
4、得到WechatUserInfo类的实例,其中就是解密好的数据。

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using System.Security.Cryptography;
using System.Text;

namespace BroadSky.WeChatAppDecrypt
{
    /// 
    /// 处理微信小程序用户数据的签名验证和解密
    /// 
    public class WeChatAppDecrypt
    {
        private string appId;
        private string appSecret;

        /// 
        /// 构造函数
        /// 
        /// 应用程序的AppId
        /// 应用程序的AppSecret
        public WeChatAppDecrypt(string appId, string appSecret)
        {
            this.appId = appId;
            this.appSecret = appSecret;
            return;
        }

        /// 
        /// 获取OpenId和SessionKey的Json数据包
        /// 
        /// 客户端发来的code
        /// Json数据包
        private string GetOpenIdAndSessionKeyString(string code)
        {
            string temp = "https://api.weixin.qq.com/sns/jscode2session?" + 
                "appid=" + appId
                + "&secret=" + appSecret
                + "&js_code=" + code 
                + "&grant_type=authorization_code";

            return GetResponse(temp);

        }

        /// 
        /// 反序列化包含OpenId和SessionKey的Json数据包
        /// 
        /// Json数据包
        /// 包含OpenId和SessionKey的类
        public OpenIdAndSessionKey DecodeOpenIdAndSessionKey(WechatLoginInfo loginInfo)
        {
            OpenIdAndSessionKey oiask = JsonConvert.DeserializeObject(GetOpenIdAndSessionKeyString(loginInfo.code));
            if (!String.IsNullOrEmpty(oiask.errcode))
                return null;
            return oiask;
        }

        /// 
        /// 根据微信小程序平台提供的签名验证算法验证用户发来的数据是否有效
        /// 
        /// 公开的用户资料
        /// 公开资料携带的签名信息
        /// 从服务端获取的SessionKey
        /// True:资料有效,False:资料无效
        public bool VaildateUserInfo(string rawData, string signature, string sessionKey)
        {
            //创建SHA1签名类
            SHA1 sha1 = new SHA1CryptoServiceProvider();
            //编码用于SHA1验证的源数据
            byte[] source = Encoding.UTF8.GetBytes(rawData + sessionKey);
            //生成签名
            byte[] target = sha1.ComputeHash(source);
            //转化为string类型,注意此处转化后是中间带短横杠的大写字母,需要剔除横杠转小写字母
            string result = BitConverter.ToString(target).Replace("-","").ToLower();
            //比对,输出验证结果
            return signature == result;
        }

        /// 
        /// 根据微信小程序平台提供的签名验证算法验证用户发来的数据是否有效
        /// 
        /// 登陆信息
        /// 从服务端获取的SessionKey
        /// True:资料有效,False:资料无效
        public bool VaildateUserInfo(WechatLoginInfo loginInfo, string sessionKey)
        {
            return VaildateUserInfo(loginInfo.rawData, loginInfo.signature, sessionKey);
        }

        /// 
        /// 根据微信小程序平台提供的签名验证算法验证用户发来的数据是否有效
        /// 
        /// 登陆信息
        /// 包含OpenId和SessionKey的类
        /// True:资料有效,False:资料无效
        public bool VaildateUserInfo(WechatLoginInfo loginInfo, OpenIdAndSessionKey idAndKey)
        {
            return VaildateUserInfo(loginInfo, idAndKey.session_key);
        }

        /// 
        /// 根据微信小程序平台提供的解密算法解密数据
        /// 
        /// 加密数据
        /// 初始向量
        /// 从服务端获取的SessionKey
        /// 
        public WechatUserInfo Decrypt(string encryptedData, string iv, string sessionKey)
        {
            WechatUserInfo userInfo;
            //创建解密器生成工具实例
            AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
            //设置解密器参数
            aes.Mode = CipherMode.CBC;
            aes.BlockSize = 128;
            aes.Padding = PaddingMode.PKCS7;
            //格式化待处理字符串
            byte[] byte_encryptedData = Convert.FromBase64String(encryptedData);
            byte[] byte_iv = Convert.FromBase64String(iv);
            byte[] byte_sessionKey = Convert.FromBase64String(sessionKey);

            aes.IV = byte_iv;
            aes.Key = byte_sessionKey;
            //根据设置好的数据生成解密器实例
            ICryptoTransform transform = aes.CreateDecryptor();
            
            //解密
            byte [] final = transform.TransformFinalBlock(byte_encryptedData, 0, byte_encryptedData.Length);

            //生成结果
            string result = Encoding.UTF8.GetString(final);

            //反序列化结果,生成用户信息实例
            userInfo = JsonConvert.DeserializeObject(result);

            return userInfo;

        }

        /// 
        /// 根据微信小程序平台提供的解密算法解密数据,推荐直接使用此方法
        /// 
        /// 登陆信息
        /// 用户信息
        public WechatUserInfo Decrypt(WechatLoginInfo loginInfo)
        {
            if (loginInfo == null)
                return null;

            if (String.IsNullOrEmpty(loginInfo.code))
                return null;

            OpenIdAndSessionKey oiask = DecodeOpenIdAndSessionKey(loginInfo);

            if (oiask == null)
                return null;

            if (!VaildateUserInfo(loginInfo, oiask))
                return null;

            WechatUserInfo userInfo = Decrypt(loginInfo.encryptedData, loginInfo.iv, oiask.session_key);

            return userInfo;
        }

        /// 
        /// GET请求
        /// 
        /// 
        /// 
        private string GetResponse(string url)
        {
            if (url.StartsWith("https"))
                System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

            HttpClient httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
            HttpResponseMessage response = httpClient.GetAsync(url).Result;

            if (response.IsSuccessStatusCode)
            {
                string result = response.Content.ReadAsStringAsync().Result;
                return result;
            }
            return null;
        }

        
    }
    /// 
    /// 微信小程序登录信息结构
    /// 
    public class WechatLoginInfo
    {
        public string code { get; set; }
        public string encryptedData { get; set; }
        public string iv { get; set; }
        public string rawData { get; set; }
        public string signature { get; set; }
    }
    /// 
    /// 微信小程序用户信息结构
    /// 
    public class WechatUserInfo
    {
        public string openId { get; set; }
        public string nickName { get; set; }
        public string gender { get; set; }
        public string city { get; set; }
        public string province { get; set; }
        public string country { get; set; }
        public string avatarUrl { get; set; }
        public string unionId { get; set; }
        public Watermark watermark { get; set; }

        public class Watermark
        {
            public string appid { get; set; }
            public string timestamp { get; set; }
        }
    }
    /// 
    /// 微信小程序从服务端获取的OpenId和SessionKey信息结构
    /// 
    public class OpenIdAndSessionKey
    {
        public string openid { get; set; }
        public string session_key { get; set; }
        public string errcode { get; set; }
        public string errmsg { get; set; }
    }
}
获取用户解密信息,并上传到数据库

using AIOWeb.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;

namespace AIOWeb
{
    /// 
    /// wxapi 的摘要说明
    /// 
    public class wxapi : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";

            string code = "";
            string iv = "";
            string encryptedData = "";
            try
            {
                code = HttpContext.Current.Request.QueryString["code"].ToString();
                iv = HttpContext.Current.Request.QueryString["iv"].ToString();
                encryptedData = HttpContext.Current.Request.QueryString["encryptedData"].ToString();
            }
            catch (Exception ex)
            {
                context.Response.Write(ex.ToString());
            }

            string Appid = "wxdb2641f85b04f1b3";
            string Secret = "8591d8cd7197b9197e17b3275329a1e7";
            string grant_type = "authorization_code";

            //向微信服务端 使用登录凭证 code 获取 session_key 和 openid 
            string url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + Appid + "&secret=" + Secret + "&js_code=" + code + "&grant_type=" + grant_type;
            string type = "utf-8";

            AIOWeb.Models.GetUsersHelper GetUsersHelper = new AIOWeb.Models.GetUsersHelper();
            string j = GetUsersHelper.GetUrltoHtml(url, type);//获取微信服务器返回字符串

            //将字符串转换为json格式
            JObject jo = (JObject)JsonConvert.DeserializeObject(j);

            result res = new result();
            try
            {
                //微信服务器验证成功
                res.openid = jo["openid"].ToString();
                res.session_key = jo["session_key"].ToString();
            }
            catch (Exception)
            {
                //微信服务器验证失败
                res.errcode = jo["errcode"].ToString();
                res.errmsg = jo["errmsg"].ToString();
            }
            if (!string.IsNullOrEmpty(res.openid))
            {
                //用户数据解密
                GetUsersHelper.AesIV = iv;
                GetUsersHelper.AesKey = res.session_key;

                string result = GetUsersHelper.AESDecrypt(encryptedData);


                //存储用户数据
                JObject _usrInfo = (JObject)JsonConvert.DeserializeObject(result);

                userInfo userInfo = new userInfo();
                userInfo.openId = _usrInfo["openId"].ToString();

                try //部分验证返回值中没有unionId
                {
                    userInfo.unionId = _usrInfo["unionId"].ToString();
                }
                catch (Exception)
                {
                    userInfo.unionId = "unionId";
                }
                
                userInfo.nickName = _usrInfo["nickName"].ToString();
                userInfo.gender = _usrInfo["gender"].ToString();
                userInfo.city = _usrInfo["city"].ToString();
                userInfo.province = _usrInfo["province"].ToString();
                userInfo.country = _usrInfo["country"].ToString();
                userInfo.avatarUrl = _usrInfo["avatarUrl"].ToString();

                object watermark = _usrInfo["watermark"].ToString();
                object appid = _usrInfo["watermark"]["appid"].ToString();
                object timestamp = _usrInfo["watermark"]["timestamp"].ToString();


                #region


                //创建连接池对象(与数据库服务器进行连接)
                SqlConnection conn = new SqlConnection("server=127.0.0.1;database=Test;uid=sa;pwd=1");
                //打开连接池
                conn.Open();
                //创建命令对象
                string Qrystr = "SELECT * FROM WeChatUsers WHERE openId='" + userInfo.openId + "'";
                SqlCommand cmdQry = new SqlCommand(Qrystr, conn);
                object  obj = cmdQry.ExecuteScalar();
                if ((Object.Equals(obj, null)) || (Object.Equals(obj, System.DBNull.Value)))
                {
                    string str = "INSERT INTO WeChatUsers ([UnionId] ,[OpenId],[NickName],[Gender],[City],[Province],[Country],[AvatarUrl],[Appid],[Timestamp],[Memo],[counts])VALUES('" + userInfo.unionId + "','" + userInfo.openId + "','" + userInfo.nickName + "','" + userInfo.gender + "','" + userInfo.city + "','" + userInfo.province + "','" + userInfo.country + "','" + userInfo.avatarUrl + "','" + appid.ToString() + "','" + timestamp.ToString() + "','来自微信小程序','1')";

                    SqlCommand cmdUp = new SqlCommand(str, conn);
                    // 执行操作
                    try
                    {
                        int row = cmdUp.ExecuteNonQuery();
                    }
                    catch (Exception ex)
                    {
                        context.Response.Write(ex.ToString());
                    }
                }
                else
                {
                    //多次访问,记录访问次数counts   更新unionId是预防最初没有,后期关联后却仍未记录
                    string str = "UPDATE dbo.WeChatUsers SET counts = counts+1,UnionId = '" + userInfo.unionId + "' WHERE OpenId='" + userInfo.openId + "'";
                    SqlCommand cmdUp = new SqlCommand(str, conn);
                    int row = cmdUp.ExecuteNonQuery();
                }
               
                //关闭连接池
                conn.Close();
                #endregion

                //返回解密后的用户数据
                context.Response.Write(result);
            }
            else
            {
                context.Response.Write(j);
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

GetUsersHelper 帮助类

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace AIOWeb.Models
{
    public class GetUsersHelper
    {

        /// 
        /// 获取链接返回数据
        /// 
        /// 链接
        /// 请求类型
        /// 
        public  string GetUrltoHtml(string Url, string type)
        {
            try
            {
                System.Net.WebRequest wReq = System.Net.WebRequest.Create(Url);
                // Get the response instance.
                System.Net.WebResponse wResp = wReq.GetResponse();
                System.IO.Stream respStream = wResp.GetResponseStream();
                // Dim reader As StreamReader = New StreamReader(respStream)
                using (System.IO.StreamReader reader = new System.IO.StreamReader(respStream, Encoding.GetEncoding(type)))
                {
                    return reader.ReadToEnd();
                }
            }
            catch (System.Exception ex)
            {
                return ex.Message;
            }
        }
        #region 微信小程序用户数据解密

        public static string AesKey;
        public static string AesIV;

        /// 
        /// AES解密
        /// 
        /// 输入的数据encryptedData
        /// key
        /// 向量128
        /// 解密后的字符串
        public string AESDecrypt(string inputdata)
        {
            try
            {
                AesIV = AesIV.Replace(" ", "+");
                AesKey = AesKey.Replace(" ", "+");
                inputdata = inputdata.Replace(" ", "+");
                byte[] encryptedData = Convert.FromBase64String(inputdata);

                RijndaelManaged rijndaelCipher = new RijndaelManaged();
                rijndaelCipher.Key = Convert.FromBase64String(AesKey); // Encoding.UTF8.GetBytes(AesKey);
                rijndaelCipher.IV = Convert.FromBase64String(AesIV);// Encoding.UTF8.GetBytes(AesIV);
                rijndaelCipher.Mode = CipherMode.CBC;
                rijndaelCipher.Padding = PaddingMode.PKCS7;
                ICryptoTransform transform = rijndaelCipher.CreateDecryptor();
                byte[] plainText = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
                string result = Encoding.UTF8.GetString(plainText);

                return result;
            }
            catch (Exception)
            {
                return null;

            }
        }
        #endregion
    }
}

实体类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace AIOWeb.Models
{
    public class wechat
    {
    }
    #region 实体类
    /// 
    /// 微信用户类
    /// 
    public class userInfo
    {
        public string openId { get; set; }
        public string nickName { get; set; }
        public string gender { get; set; }
        public string city { get; set; }
        public string province { get; set; }
        public string country { get; set; }
        public string avatarUrl { get; set; }
        public string unionId { get; set; }
        public data watermark { get; set; }
    }
    /// 
    /// 微信用户数据水印
    /// 
    public class data
    {
        public string appid { get; set; }
        public string timestamp { get; set; }
    }
    /// 
    /// 微信小程序验证返回结果
    /// 
    public class result
    {
        public string openid { get; set; }
        public string session_key { get; set; }
        public string errcode { get; set; }
        public string errmsg { get; set; }
    }
    #endregion
}

你可能感兴趣的:(.NET)