In my previous post, I demonstrated how to migrate from ASP.NET Universal Membership Provider to ASP.NET Identity 2.1.0.

https://kontext.tech/Blog/DotNetEssential/Archive/2015/1/31/migrate-from-systemwebproviders-to-aspnet-identity-10-and-then-to-210.html

Based on the researches, I have consolidated one ApplicationUserManager class to help you handle the password rehash. It also added two properties for keeping legacy password format.

Code for ApplicationUserManager class

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using System;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace KosmischStudio.Website.Models
{
    public class ApplicationUserManager : UserManager<ApplicationUser>
    {
        public ApplicationUserManager()
            : base(new UserStore<ApplicationUser>(new ApplicationDbContext()))
        {
            this.PasswordHasher = new SqlPasswordHasher()
            {
                KeepLegacyHashFormat = false,
                PasswordFormat = 1
            };
        }

        protected async override Task<bool> VerifyPasswordAsync(IUserPasswordStore<ApplicationUser, string> store, ApplicationUser user, string password)
        {
            var hash = await store.GetPasswordHashAsync(user).ConfigureAwait(false);

            if (this.PasswordHasher.VerifyHashedPassword(hash, password) == PasswordVerificationResult.SuccessRehashNeeded)
            {
                // Make our new hash
                hash = PasswordHasher.HashPassword(password);

                // Save it to the DB
                await store.SetPasswordHashAsync(user, hash).ConfigureAwait(false);

                // Invoke internal method to upgrade the security stamp
                BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
                MethodInfo minfo = typeof(UserManager<ApplicationUser>).GetMethod("UpdateSecurityStampInternal", bindingFlags);
                var updateSecurityStampInternalTask = (Task)minfo.Invoke(this, new[] { user });
                await updateSecurityStampInternalTask.ConfigureAwait(false);

                // Update user
                await UpdateAsync(user).ConfigureAwait(false);
            }

            return PasswordHasher.VerifyHashedPassword(hash, password) != PasswordVerificationResult.Failed;
        }
    }

    public class SqlPasswordHasher : PasswordHasher
    {
        /// <summary>
        /// Whether to keep the legacy hash format: {$HashedPassword}|{$Format}|{$Salt}
        /// </summary>
        /// <returns></returns>
        public bool KeepLegacyHashFormat { get; set; }

        /// <summary>
        /// Format of the password: 1 : Hashed, 0 Clear, 2 Encrypt
        /// </summary>
        /// <returns></returns>
        public int PasswordFormat { get; set; }

        public override string HashPassword(string password)
        {

            if (KeepLegacyHashFormat)
            {
                StringBuilder passwordNew = new StringBuilder();
                string salt = GenerateSalt();
                string hashedPassword = EncryptPassword(password, PasswordFormat, salt);
                StringBuilder newPassword = new StringBuilder();
                newPassword.AppendFormat("{0}|{1}|{2}", hashedPassword, PasswordFormat, salt);
                return newPassword.ToString();
            }
            else
                return base.HashPassword(password);
        }

        private static string GenerateSalt()
        {
            string base64String;
            using (RNGCryptoServiceProvider rNGCryptoServiceProvider = new RNGCryptoServiceProvider())
            {
                byte[] numArray = new byte[16];
                rNGCryptoServiceProvider.GetBytes(numArray);
                base64String = Convert.ToBase64String(numArray);
            }
            return base64String;
        }

        public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
        {
            string[] passwordProperties = hashedPassword.Split('|');
            if (passwordProperties.Length != 3)
            {
                return base.VerifyHashedPassword(hashedPassword, providedPassword);
            }
            else
            {
                string passwordHash = passwordProperties[0];
                int passwordformat = 1;
                string salt = passwordProperties[2];
                if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase))
                {
                    if (!this.KeepLegacyHashFormat)
                        return PasswordVerificationResult.SuccessRehashNeeded;
                    else
                        return PasswordVerificationResult.Success;
                }
                else
                {
                    return PasswordVerificationResult.Failed;
                }
            }
        }



        //This is copied from the existing SQL providers and is provided only for back-compat.
        private string EncryptPassword(string pass, int passwordFormat, string salt)
        {
            if (passwordFormat == 0) // MembershipPasswordFormat.Clear
                return pass;

            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bRet = null;

            if (passwordFormat == 1)
            { // MembershipPasswordFormat.Hashed 
                HashAlgorithm hm = HashAlgorithm.Create("SHA1");
                if (hm is KeyedHashAlgorithm)
                {
                    KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
                    if (kha.Key.Length == bSalt.Length)
                    {
                        kha.Key = bSalt;
                    }
                    else if (kha.Key.Length < bSalt.Length)
                    {
                        byte[] bKey = new byte[kha.Key.Length];
                        Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
                        kha.Key = bKey;
                    }
                    else
                    {
                        byte[] bKey = new byte[kha.Key.Length];
                        for (int iter = 0; iter < bKey.Length;)
                        {
                            int len = Math.Min(bSalt.Length, bKey.Length - iter);
                            Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
                            iter += len;
                        }
                        kha.Key = bKey;
                    }
                    bRet = kha.ComputeHash(bIn);
                }
                else
                {
                    byte[] bAll = new byte[bSalt.Length + bIn.Length];
                    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
                    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
                    bRet = hm.ComputeHash(bAll);
                }
            }

            return Convert.ToBase64String(bRet);
        }
    }
}
 

Issues – PasswordHarsher doesn’t work well for legacy password.

I have been spending almost one day to investigate why I could not get the same hashed password by using same clear password, salt and encryption algorithm.

Until, I have not found any issues with the code provided. A new post will be published to address this issue. Stay tuned.


info Last modified by Raymond at 9 months ago copyright This page is subject to Site terms.

More from Kontext

local_offer asp.net core local_offer identity core 2

visibility 22009
thumb_up 0
access_time 3 years ago

The identity system in ASP.NET has evolved over time. If you are using ASP.NET Core, you probably found User property is an instance of ClaimsPrincipal in Controller or Razor views. Thus to retrieve the information, you need to utilize the claims.

open_in_new ASP.NET Core

local_offer .NET local_offer ASP.NET local_offer dotnet core local_offer asp.net core 2

visibility 697
thumb_up 0
access_time 4 years ago

Context Before Visual Studio 2017 was released, I was implementing my ASP.NET Core project using VS2015. TFS2015 is used for Continuous Build and Deployment for my projects. After migrating to VS2017, there are couple of issues I encountered in my environment: VS2017 has ...

open_in_new .NET Framework

local_offer identity core 2

visibility 2652
thumb_up 0
access_time 6 years ago

Migration from simple ASP.NET membership provider to ASP.NET Identity is not straightforward. In order to migrate smoothly, we can firstly upgrade to Identity 1.0 and then to the latest version 2.0. Reference ...

open_in_new ASP.NET

local_offer ASP.NET local_offer MVC local_offer WebMatrix

visibility 704
thumb_up 0
access_time 9 years ago

这一章节将说明怎样使用一个帮助类在HTML表格中(在一个网格中)展示数据。 你将学到以下内容: • 怎样在一个网页中使用WebGrid 帮助类展示数据。 • 怎样为展示在网格中的数据设置样式。 • 怎样为网格设置分页。 本章将介绍如下ASP.NET编程特性: • WebGrid 帮助类.

open_in_new ASP.NET

comment Comments (2)

comment Add comment

Please log in or register to comment.

account_circle Log in person_add Register

Log in with external accounts

account_circle R
HashPassword method can be constomed based on your previous settings.
reply Reply
account_circle Raymond
This issue has now been resolved, please refer to the following post: http://kosmisch.net/Blog/DotNetEssential/Archive/2015/2/1/aspnet-membership-default-password-hash-algorithms-in-net-4x-and-previous-versions.html Root cause: I was migrating from .NET 4.0 and the default hash algorithm is HMAC256 instead of SHA1.
reply Reply

Kontext Column

Created for everyone to publish data, programming and cloud related articles. Follow three steps to create your columns.


Learn more arrow_forward