Exemplo n.º 1
0
        // This code is more complicated than necessary IF you do not care about attackers enumerating your user list via timing attacks
        public ActionResult Login(string username, string password)
        {
            // Do profiling to determine what the optimal time and memory cost would be for your server setup
            // Ideally, PasswordHasher.Hash should take about 200ms to perform as a good balance between security and responsiveness
            // Prefer to increase MemoryCost instead of TimeCost (increasing MemoryCost will also increase the overall time)
            // TODO: Edit your web.config or app.config to contain the following keys within your <configuration><appSettings>...</appSettings></configuration>:
            // TODO: <add key="PasswordHasher.TimeCost" value="3" />
            // TODO: <add key="PasswordHasher.MemoryCost" value="65536" />
            // If you are updating the time cost or memory cost (because, e.g., you moved to faster hardware) then 
            // add PasswordHasher.OldTimeCost and PasswordHasher.OldMemoryCost keys with the previous values to ensure timing attacks cannot
            // be done to enumerate the possible users. Remove OldTimeCost and OldMemoryCost when the migration is finished.

            string timeCostStr = (ConfigurationManager.AppSettings["PasswordHasher.TimeCost"] ?? "3");
            string memoryCostStr = (ConfigurationManager.AppSettings["PasswordHasher.MemoryCost"] ?? "65536");
            uint timeCost = uint.Parse(timeCostStr);
            uint memoryCost = uint.Parse(memoryCostStr);
            uint oldTimeCost = uint.Parse(ConfigurationManager.AppSettings["PasswordHasher.OldTimeCost"] ?? timeCostStr);
            uint oldMemoryCost = uint.Parse(ConfigurationManager.AppSettings["PasswordHasher.OldMemoryCost"] ?? memoryCostStr);

            bool costsDiffer = (timeCost != oldTimeCost || memoryCost != oldMemoryCost);

            string hashFormat = "$argon2i$m={0},t={1},p=1$AAAAAAAAAAAAAAAAAAAAAA$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
            string dummyHash = string.Format(hashFormat, memoryCost, timeCost);
            string oldDummyHash = string.Format(hashFormat, oldMemoryCost, oldTimeCost);

            var passwordHasher = new PasswordHasher(timeCost, memoryCost);
            
            // Get the user from the database in hopefully some constant-time fashion so that timing attacks
            // cannot be used to enumerate valid usernames
            User user = GetUser(username);
            string passwordHash = (user != null ? user.PasswordHash : dummyHash);
            bool updatedCost;
            string newPasswordHash;

            HashMetadata hashMetadata = PasswordHasher.ExtractMetadata(passwordHash);
            bool usingOldCosts = (hashMetadata.MemoryCost != memoryCost || hashMetadata.TimeCost != timeCost);

            // always compare against a password hash even if the user is not found (when that happens, we compare against dummyHash)
            // to prevent timing attacks from enumerating a list of valid usernames
            if (passwordHasher.VerifyAndUpdate(passwordHash, password, out updatedCost, out newPasswordHash))
            {
                // VerifyAndUpdate will generate a new password hash if the time or memory cost from ConfigurationManager.AppSettings
                // does not match what was used to generate the password hash which was stored in the database.
                // This allows you to easily update the password hashing cost if you upgrade to better server hardware by
                // modifying the PasswordHasher.TimeCost and PasswordHasher.MemoryCost parameters.
                if (updatedCost)
                {
                    // As users successfully log in, update their password hash to the hash with the updated cost
                    user.PasswordHash = newPasswordHash;
                    UpdateUser(user);
                }
                else if(costsDiffer)
                {
                    // run the verification with the old costs so that each login attempt performs a hash
                    // with the new costs and with the old costs to ensure the timing is consistent
                    passwordHasher.Verify(oldDummyHash, password);
                }

                // Successful login
                LogInUser(user);
                return RedirectToAction("Index", "Home");
            }

            // if we're migrating password hashes from the old cost parameters to new cost parameters, we want
            // each login attempt to perform a single hash with the new cost parameters and a single hash with
            // the old cost parameters so that the timing is consistent.
            if(costsDiffer)
                passwordHasher.Verify((usingOldCosts ? dummyHash : oldDummyHash), password);

            // User failed to login
            var model = new LoginViewModel {ErrorMessage = "Username or Password is incorrect."};
            return View(model);
        }
Exemplo n.º 2
0
 // Dummy method to make the example compile
 private ActionResult View(LoginViewModel model)
 {
     return new ActionResult();
 }