1. 程式人生 > >WebAPI 使用者認證防篡改實現HMAC(一)MD5簽名獲取

WebAPI 使用者認證防篡改實現HMAC(一)MD5簽名獲取

在開始前先說下HMAC防篡改機制的原理,如果已經接觸過支付寶的可以跳過此部分

防篡改,顧名思義就是防止有人惡意篡改請求資料以達到惡意攻擊的目的,那要怎麼才能實現這樣的目的呢?其實很簡單,將要請求的資料加上合作號、合作Key按規則組織成一個字串,獲取對應的MD5摘要,然後將該摘要及合作號同時作為請求的一部分一起傳遞(合作Key禁止傳遞

下面進行舉例:

假定需要進行簽名的引數如下(以json格式舉例):

{‘partner: ‘3122131212’,‘orderNo’:‘1234567’}

對數組裡的每一個鍵按預設的字母正向排序,最後加上partner對應的key進行MD5加密,假定對應的key為bbb,則需要進行MD5摘要的字串如下:

partner=3122131212&orderNo=1234567bbb

最終需要傳遞的請求資料格式如下(分別列舉GET和POST方式,服務實際支援哪種方式以服務宣告為準):

GET:partner=3122131212&orderNo=1234567&sign=EBFE84D02E8E40952899EE5CDFE5404C

POST{‘partner:‘3122131212’,‘orderNo’:‘1234567’ ,‘sign:‘EBFE84D02E8E40952899EE5CDFE5404C’}

上例中partner為平臺提供的合作號,key為合作密碼,sign為簽名,然後此處例子是將Get和Post區分開來的,實際獲取MD5的程式碼會支援QueryString及Form同時存在的情況,且當兩者同時存在時,組織字串順序為先QueryString後Form

以下是簽名摘要生成程式碼

    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Security.Cryptography;
    using System.Text;

    public static class SecuritySignHelper
    {
        public const string Partner = "partner";
        public const string Sign = "sign";
        /// <summary>
        /// 獲取防篡改簽名,組織原始字串的方式為:先get後post,該簽名要求partner在加密時為全小寫,同時該方法隱含要求parnter和sign必須通過QueryString方式傳遞
        /// </summary>
        /// <param name="getCollection">通過QueryString方式傳遞的鍵值集合,如果內部包含parnter或者sign,相關欄位在組織原始字串時將會被移除</param>
        /// <param name="partner">合作賬號</param>
        /// <param name="partnerKey">合作Key</param>
        /// <param name="postCollection">通過Form方式傳遞的鍵值集合,如果包含parnter或者sign,此部分不會被做特殊處理</param>
        /// <returns></returns>
        public static string GetSecuritySign(this NameValueCollection getCollection, string partner, string partnerKey, NameValueCollection postCollection = null)
        {
            if (string.IsNullOrWhiteSpace(partner) || string.IsNullOrWhiteSpace(partnerKey))
            {
                throw new ArgumentNullException();
            }
            var dic = SecuritySignHelper.GetSortedDictionary(getCollection,
                (k) =>
                {//過濾partner及sign
                    return string.Equals(k, SecuritySignHelper.Partner, StringComparison.OrdinalIgnoreCase)
                        || string.Equals(k, SecuritySignHelper.Sign, StringComparison.OrdinalIgnoreCase);
                });
            dic.Add(SecuritySignHelper.Partner, partner);
            StringBuilder tmp = new StringBuilder();
            SecuritySignHelper.FillStringBuilder(tmp, dic);//將QueryString填入StringBuilder
            dic = SecuritySignHelper.GetSortedDictionary(postCollection);
            SecuritySignHelper.FillStringBuilder(tmp, dic);//將Form填入StringBuilder
            tmp.Append(partnerKey);//在尾部新增key
            tmp.Remove(0, 1);//移除第一個&
            return tmp.ToString().GetMD5_32();//獲取32位長度的Md5摘要
        }
        private static SortedDictionary<string, string> GetSortedDictionary(NameValueCollection collection, Func<string, bool> filter = null)
        {//獲取排序的鍵值對
            SortedDictionary<string, string> dic = new SortedDictionary<string, string>();
            if (collection != null && collection.Count > 0)
            {
                foreach (var k in collection.AllKeys)
                {
                    if (filter == null || !filter(k))
                    {//如果沒設定過濾條件或者無需過濾
                        dic.Add(k, collection[k]);
                    }
                }
            }
            return dic;
        }
        private static void FillStringBuilder(StringBuilder builder, SortedDictionary<string, string> dic)
        {
            foreach (var kv in dic)
            {
                builder.Append('&');
                builder.Append(kv.Key);
                builder.Append('=');
                builder.Append(kv.Value);
            }//按key順序組織字串
        }

        /// <summary>
        /// 獲取32位長度的Md5摘要
        /// </summary>
        /// <param name="inputStr"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static string GetMD5_32(this string inputStr, Encoding encoding = null)
        {
            RefEncoding(ref encoding);
            byte[] data = GetMD5(inputStr, encoding);
            StringBuilder tmp = new StringBuilder();
            for (int i = 0; i < data.Length; i++)
            {
                tmp.Append(data[i].ToString("x2"));
            }
            return tmp.ToString();
        }
        private static byte[] GetMD5(string inputStr, Encoding encoding)
        {
            using (MD5 md5Hash = MD5.Create())
            {
                return md5Hash.ComputeHash(encoding.GetBytes(inputStr));
            }
        }
        private static void RefEncoding(ref Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.Default;
            }
        }
    }
使用例子
            string partner = "zhangsan";
            string partnerKey = "bbb";
            NameValueCollection getCollection = new NameValueCollection();
            string md50 = getCollection.GetSecuritySign(partner, partnerKey);//不帶任何引數的請求
            getCollection.Add("test", "123");
            string md51 = getCollection.GetSecuritySign(partner, partnerKey);//帶一個引數的請求
            NameValueCollection postCollection = new NameValueCollection();
            postCollection.Add("Name", "張三");
            postCollection.Add("Address", "上海");
            string md52 = getCollection.GetSecuritySign(partner, partnerKey, postCollection);//同時存在QueryString及Form的請求