asp. net mvc webapi Practical Interface Encryption Method Example

  • 2021-10-15 10:17:56
  • OfStack

In many projects, because webapi is open to the outside world, at this time, we have to consider the security of interface exchange data.

There are many security mechanisms. For example, when andriod and webapi exchange data, they can take the two-way certificate method, but the development cost is relatively high.

Today, we are not going to introduce this knowledge. Let's talk about a simpler and more common secure exchange mechanism

Here, we should remind readers that all encryption mechanisms are not absolutely safe at present!

Our goal is that any user or software that acquires our webapi interface url and then uses it to access the address again is invalid!

To achieve this goal, we must add a timestamp in url, but this is not enough. Users can modify our timestamp!

So we can time stamp MD5 encryption, but this is still not enough, users can directly to our time stamp md5 oh, because of some need to introduce an absolute security

key agreed by both parties, and add other parameters for confusion at the same time!

Note: This key should be kept in one copy of app and one copy of our webapi!

So we agreed on the formula: encryption result = md5 (timestamp + random number + key+post or get parameters)

Let's start writing code through the above formula:

Since my environment is asp. net mvc, rewrite 1 encryption class ApiSecurityFilter

1. Get parameters


if (request.Headers.Contains("timestamp"))
    timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());

   if (request.Headers.Contains("nonce"))
    nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());

   if (request.Headers.Contains("signature"))
    signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());

   if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature))
    throw new SecurityException();

2. Judge whether the time stamp exceeds the specified time


 double ts = 0;
   bool timespanvalidate = double.TryParse(timestamp, out ts);

   bool falg = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new SecurityException();

3. POST/DELETE/UPDATE three ways to extract parameters


 case "POST":
    case "PUT":
    case "DELETE":

     Stream stream = HttpContext.Current.Request.InputStream;
     StreamReader streamReader = new StreamReader(stream);
     sortedParams = new SortedDictionary<string, string>(new JsonSerializer().Deserialize<Dictionary<string, string>>(new JsonTextReader(streamReader)));

     break;

4. Extracting parameters in GET mode


case "GET":

     IDictionary<string, string> parameters = new Dictionary<string, string>();

     foreach (string key in HttpContext.Current.Request.QueryString)
     {
      if (!string.IsNullOrEmpty(key))
      {
       parameters.Add(key, HttpContext.Current.Request.QueryString[key]);
      }
     }

     sortedParams = new SortedDictionary<string, string>(parameters);

     break;

5. Sort the above parameters and splice them to form the fourth parameter in the convention formula that we want to participate in md5


   StringBuilder query = new StringBuilder();

   if (sortedParams != null)
   {
    foreach (var sort in sortedParams.OrderBy(k => k.Key))
    {
     if (!string.IsNullOrEmpty(sort.Key))
     {
      query.Append(sort.Key).Append(sort.Value);
     }
    }

    data = query.ToString().Replace(" ", "");
   }

6. Start to agree on the calculation results of the formula and compare whether the transmitted results are 1


 var md5Staff = Seedwork.Utils.CharHelper.MD5(string.Concat(timestamp + nonce + staffId + data), 32);

   if (!md5Staff.Equals(signature))
    throw new SecurityException();

The complete code is as follows:


public class ApiSecurityFilter : ActionFilterAttribute
 {
  public override void OnActionExecuting(HttpActionContext actionContext)
  {
   var request = actionContext.Request;

   var method = request.Method.Method;
   var staffId = "^***********************************$";

   string timestamp = string.Empty, nonce = string.Empty, signature = string.Empty;

   if (request.Headers.Contains("timestamp"))
    timestamp = request.Headers.GetValues("timestamp").FirstOrDefault();

   if (request.Headers.Contains("nonce"))
    nonce = request.Headers.GetValues("nonce").FirstOrDefault();

   if (request.Headers.Contains("signature"))
    signature = request.Headers.GetValues("signature").FirstOrDefault();

   if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(signature))
    throw new SecurityException();

   double ts = 0;
   bool timespanvalidate = double.TryParse(timestamp, out ts);

   bool falg = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new SecurityException("timeSpanValidate");

   var data = string.Empty;
   IDictionary<string, string> sortedParams = null;

   switch (method.ToUpper())
   {
    case "POST":
    case "PUT":
    case "DELETE":

     Stream stream = HttpContext.Current.Request.InputStream;
     StreamReader streamReader = new StreamReader(stream);
     sortedParams = new SortedDictionary<string, string>(new JsonSerializer().Deserialize<Dictionary<string, string>>(new JsonTextReader(streamReader)));

     break;
     
    case "GET":

     IDictionary<string, string> parameters = new Dictionary<string, string>();

     foreach (string key in HttpContext.Current.Request.QueryString)
     {
      if (!string.IsNullOrEmpty(key))
      {
       parameters.Add(key, HttpContext.Current.Request.QueryString[key]);
      }
     }

     sortedParams = new SortedDictionary<string, string>(parameters);

     break;

    default:
     throw new SecurityException("defaultOptions");
   }

   StringBuilder query = new StringBuilder();

   if (sortedParams != null)
   {
    foreach (var sort in sortedParams.OrderBy(k => k.Key))
    {
     if (!string.IsNullOrEmpty(sort.Key))
     {
      query.Append(sort.Key).Append(sort.Value);
     }
    }

    data = query.ToString().Replace(" ", "");
   }
  
   var md5Staff = Seedwork.Utils.CharHelper.MD5(string.Concat(timestamp + nonce + staffId + data), 32);

   if (!md5Staff.Equals(signature))
    throw new SecurityException("md5Staff");

   base.OnActionExecuting(actionContext);
  }

  public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
  {
   base.OnActionExecuted(actionExecutedContext);
  }
 }

7. Finally, add and configure the above class in asp. net mvc


 public static class WebApiConfig
 {
  public static void Register(HttpConfiguration config)
  {
   // Web API configuration and services
   config.Filters.Add(new ApiSecurityFilter());

   config.Filters.Add(new ApiHandleErrorAttribute());

   // Web API routes
   config.MapHttpAttributeRoutes();

   config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
   );
  }
 }

8. Add a write log class


 public class ApiHandleErrorAttribute: ExceptionFilterAttribute
 {
  /// <summary>
  /// add by laiyunba 
  /// </summary>
  /// <param name="filterContext">context oop</param>
  public override void OnException(HttpActionExecutedContext filterContext)
  {
   LoggerFactory.CreateLog().LogError(Messages.error_unmanagederror, filterContext.Exception);
  }
 }

9. Use WeChat Applet to test the interface


 var data = {
  UserName: username,
  Password: password,
  Action: 'Mobile',
  Sms: ''
  };

  var timestamp = util.gettimestamp();
  var nonce = util.getnonce();

  if (username && password) {
  wx.request({
   url: rootUrl + '/api/login',
   method: "POST",
   data: data,
   header: {
   'content-type': 'application/json',
   'timestamp': timestamp,
   'nonce': nonce,
   'signature': util.getMD5Staff(data, timestamp, nonce)
   },
   success: function (res) {
   if (res.data) {

1) Where the getMD5Staff function:


 double ts = 0;
   bool timespanvalidate = double.TryParse(timestamp, out ts);

   bool falg = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new SecurityException();
0

2) dictionaryOrderWithData function:


 double ts = 0;
   bool timespanvalidate = double.TryParse(timestamp, out ts);

   bool falg = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new SecurityException();
1

10. Test log


 double ts = 0;
   bool timespanvalidate = double.TryParse(timestamp, out ts);

   bool falg = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds - ts > 60 * 1000;

   if (falg || (!timespanvalidate))
    throw new SecurityException();
2

So far, the encryption work of webapi has been completed, and the above exception is an error reported by direct access to url, which must be accessed normally under app environment.

Summary: There are many encryption secrets of webapi. Like WeChat applet, it is difficult for users to get the source code of client app, and it is impossible to talk about our key. Of course, we have to update the app version regularly.

Like app for andriod or ios, we can use two-way certificates, or use the above methods, and then reinforce app to prevent malicious people from cracking key. Of course, no matter what, the first thing we have to go is https protocol!


Related articles: