using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; namespace AipGateway.Log { /// /// Token-based authentication for ASP .NET MVC REST web services. /// Copyright (c) 2015 Kory Becker /// http://primaryobjects.com/kory-becker /// License MIT /// public static class SecurityManager { private const string _alg = "HmacSHA256"; private const string _salt = "rz8LuOtFBXphj9WQfvFh"; private static int _expirationMinutes = 10; /// /// Generates a token to be used in API calls. /// The token is generated by hashing a message with a key, using HMAC SHA256. /// The message is: username:ip:userAgent:timeStamp /// The key is: password:ip:salt /// The resulting token is then concatenated with username:timeStamp and the result base64 encoded. /// /// API calls may then be validated by: /// 1. Base64 decode the string, obtaining the token, username, and timeStamp. /// 2. Ensure the timestamp is not expired. /// 2. Lookup the user's password from the db (cached). /// 3. Hash the username:ip:userAgent:timeStamp with the key of password:salt to compute a token. /// 4. Compare the computed token with the one supplied and ensure they match. /// public static string GenerateToken(string username, string password, string ip, string userAgent, long ticks) { string hash = string.Join(":", new string[] { username, ip, userAgent, ticks.ToString() }); string hashLeft = ""; string hashRight = ""; using (HMAC hmac = HMACSHA256.Create(_alg)) { hmac.Key = Encoding.UTF8.GetBytes(GetHashedPassword(password)); hmac.ComputeHash(Encoding.UTF8.GetBytes(hash)); hashLeft = Convert.ToBase64String(hmac.Hash); hashRight = string.Join(":", new string[] { username, ticks.ToString() }); } return Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(":", hashLeft, hashRight))); } /// /// Returns a hashed password + salt, to be used in generating a token. /// /// string - user's password /// string - hashed password public static string GetHashedPassword(string password) { string key = string.Join(":", new string[] { password, _salt }); using (HMAC hmac = HMACSHA256.Create(_alg)) { // Hash the key. hmac.Key = Encoding.UTF8.GetBytes(_salt); hmac.ComputeHash(Encoding.UTF8.GetBytes(key)); return Convert.ToBase64String(hmac.Hash); } } /// /// Checks if a token is valid. /// /// string - generated either by GenerateToken() or via client with cryptojs etc. /// string - IP address of client, passed in by RESTAuthenticate attribute on controller. /// string - user-agent of client, passed in by RESTAuthenticate attribute on controller. /// bool public static bool IsTokenValid(string token, string ip, string userAgent) { bool result = false; try { // Base64 decode the string, obtaining the token:username:timeStamp. string key = Encoding.UTF8.GetString(Convert.FromBase64String(token)); // Split the parts. string[] parts = key.Split(new char[] { ':' }); if (parts.Length == 3) { // Get the hash message, username, and timestamp. string hash = parts[0]; string username = parts[1]; long ticks = long.Parse(parts[2]); DateTime timeStamp = new DateTime(ticks); // Ensure the timestamp is valid. bool expired = Math.Abs((DateTime.UtcNow - timeStamp).TotalMinutes) > _expirationMinutes; if (!expired) { // // Lookup the user's account from the db. // if (username == "john") { string password = "password"; // Hash the message with the key to generate a token. string computedToken = GenerateToken(username, password, ip, userAgent, ticks); // Compare the computed token with the one supplied and ensure they match. result = (token == computedToken); } } } } catch { } return result; } } }