How to use signature to ensure the interface security of ASP.NET MVC OR WEBAPI

Time:2022-9-16

When we develop an App, the App needs to communicate with the background service to obtain or submit data. If we do not have a perfect security mechanism, it is easy for someone with ulterior motives to forge requests and tamper with data.
So we need to use some kind of security mechanism to ensure that the request is legitimate. The most common method now is to add a signature to each http request, and the server verifies the validity of the signature. If the signature is valid, the response operation is performed, and if the signature is invalid, the request is rejected directly.

Signature algorithm

The signature algorithm generally uses Hash hash algorithm, commonly used MD5, SHA series algorithms. These algorithms can calculate different results based on different inputs, and the probability of collision is very low.
A signature algorithm is not the same as an encryption algorithm. Many students will say to use MD5 encryption, in fact, this is wrong. The signature algorithm cannot recover the original data, because it does not contain the information of the original data.
The encryption method is different. The encryption method can recalculate the original data according to the encryption result.
As a more secure signature algorithm, HMAC SHA uses a Key to affect the signature result. In this way, the same input with different keys can obtain different signatures, which is more secure.


 public static string HmacSHA256(string secretKey,string plain)
        {
            var keyBytes = Encoding.UTF8.GetBytes(secretKey);
            var plainBytes = Encoding.UTF8.GetBytes(plain);

            using (var hmacsha256 = new HMACSHA256(keyBytes))
            {
                var sb = new StringBuilder();
                var hashValue = hmacsha256.ComputeHash(plainBytes);
                foreach (byte x in hashValue)
                {
                    sb.Append(String.Format("{0:x2}", x));
                }
                return sb.ToString();
            }
        }

signature parameter

With the signature algorithm, where does the content we sign come from?
Generally, we use the queryString of the http request and then add a timestamp and a random number as the signature parameters.


 public static string MakeSignPlain(SortedDictionary<string,string> queryString,string time,string random )
        {
            var sb = new StringBuilder();
            foreach (var keyValue in queryString)
            {
                sb.AppendFormat("{0}={1}&", keyValue.Key, keyValue.Value);
            }
            if (sb.Length>1)
            {
                sb.Remove(sb.Length - 1, 1);
            }
            sb.Append(time);
            sb.Append(random);

            return sb.ToString().ToUpper();
        }

Verify signature

Verifying the signature is simply comparing the signature produced by the server to the signature produced by the client.
One thing to note is that it is best to verify the timestamp, which cannot be 5 minutes away from the server time. This is also a simple means of preventing Replay Attack.


 public static bool Valid(string requestSign,string signPlain,string time, string secretKey)
        {
            if (string.IsNullOrEmpty(time)||string.IsNullOrEmpty(requestSign)||string.IsNullOrEmpty(signPlain))
            {
                return false;
            }
            //is in range
            var now = DateTime.Now;
            long requestTime =0;
            if (long.TryParse(time,out requestTime))
            {
                var max = now.AddMinutes(5).ToString("yyyyMMddHHmmss");
                var min = now.AddMinutes(-5).ToString("yyyyMMddHHmmss");
                if (!(long.Parse(max) >= requestTime && long.Parse(min) <= requestTime))
                {
                    return false;
                }
              
            }
            else
            {
                return false;
            }

            //hashmac
            var sign = Encryption.HmacSHA256(secretKey, signPlain);

            return requestSign.Equals(sign, StringComparison.CurrentCultureIgnoreCase);
        }

ApiController base class

With the above foreshadowing, we can complete the signature verification in the base class. The client needs to put the timestamp, random number, signature and client ID mentioned above into the headers of the http request.
We take these data and combine them into the parameters of the signature in the OnActionExecuting of the base class, then obtain the key of the signature according to the client ID, and then use the same signature algorithm to calculate the signature. And compare whether the client's signature is consistent with the server's signature.
It will not be demonstrated here.

Prevent Replay Attack

There are two main points to prevent replay attacks:

  • Check the range of timestamps

Timestamps that differ from the server time within a reasonable range are considered legal.

  • cache signature

Every time a request is made, it is determined whether the signature has appeared. If it occurs, it will be regarded as an illegal request.
Because of the existence of the timestamp follower number, it is theoretically impossible for the signature of each request to be repeated.

Client call

Here is a demonstration of the C# signature and the code that calls the http interface


 [TestMethod()]
        public void GetUserTest()
        {
            string url = "http://localhost:8090/api/test/GetUser";
            string userId = "A39891D4-6CEF-4538-A562-3A422CA9C17A";
            string appId = "100001";
            string secretKey = "M/vkPOWXgBa7GnRd73t7j+jsKfbZtb+f";
            string rumdon = Guid.NewGuid().ToString();
            string time = DateTime.Now.ToString("yyyyMMddHHmmss");

            //make signture plain text
            var sortDict = new SortedDictionary<string, string>()
            {
                {"userId",userId }
            };
            var signPlain = new StringBuilder();
            foreach (var keyValue in sortDict)
            {
                signPlain.AppendFormat("{0}={1}&", keyValue.Key, keyValue.Value);
            }
            if (signPlain.Length > 1)
            {
                //remove last &
                signPlain.Remove(signPlain.Length - 1, 1);
            }
            signPlain.Append(time);
            signPlain.Append(random);

            Console.WriteLine("sign plain:{0}", signPlain.ToString().ToUpper());
            //make sign
            var sign = Encryption.HmacSHA256(secretKey, signPlain.ToString().ToUpper());
            Console.WriteLine("sign:{0}", sign);

            string requestUrl = string.Format("{0}?{1}={2}", url, "userId", userId);
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUrl);
            request.Method = "GET";
            //add headers
            request.Headers.Add("time", time);
            request.Headers.Add("appId", appId);
            request.Headers.Add("random", random);
            request.Headers.Add("sign", sign);
            //
            //start request
            try
            {
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    var responseStream = response.GetResponseStream();
                    if (responseStream != null)
                    {
                        using (StreamReader reader = new StreamReader(responseStream))
                        {
                            var content = reader.ReadToEnd();

                            Console.WriteLine(content);
                        }
                    }
                }
            }
            catch (WebException ex)
            {
                using (HttpWebResponse response = (HttpWebResponse)ex.Response)
                {
                    var responseStream = response.GetResponseStream();
                    if (responseStream != null)
                    {
                        using (StreamReader reader = new StreamReader(responseStream))
                        {
                            var content = reader.ReadToEnd();
                            Console.WriteLine(content);
                        }
                    }
                }
            }
        }

The above is the detailed content of how to use the signature to ensure the interface security of ASP.NET MVC OR WEBAPI. For more information about using the signature to ensure the interface security of ASP.NET MVC OR WEBAPI, please pay attention to other related articles on developpaer!