This site uses cookies to deliver our services. By using this site, you acknowledge that you have read and understand our Cookie and Privacy policy. Your use of Kontext website is subject to this policy. Allow Cookies and Dismiss

Migrate from Universal Membership Provider to ASP.NET Identity 2.1.0 - PasswordHarsher

2254 views 2 comments last modified about 4 years ago Raymond

In this page

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

http://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.

Related pages

Issue - Unable to get property 'apply' of undefined or null reference occurred in Angular 4.*, VS2017 15.3, ASP.NET Core 2.0

6152 views   10 comments last modified about 2 years ago

Issue Context After installed Visual Studio 2017 15.3 preview and .net core 2.0 preview SDK, I upgraded one of my existing asp.net core project to 2.0. The project was created using ‘dotnet new angular’ SPA template.&nbsp; I also upgraded all the client app packages to the latest. For exa...

View detail

Retrieve Identity username, email and other information in ASP.NET Core

677 views   0 comments last modified about 9 months 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.

View detail

Retrieve Azure AppSettings and Connection String Settings in ASP.NET Core Apps

1172 views   0 comments last modified about 9 months ago

In ASP.NET Core, we can easily use user secrets to manage our password or credentials. This post will summarize the approaches we can use after the websites are deployed into Azure.

View detail

Server.MapPath Equivalent in ASP.NET Core 2

3508 views   0 comments last modified about 9 months ago

In traditional asp.net applications, Server.MapPath is commonly used to generate absolute path in the web server. However, this has been removed from ASP.NET Core. So what is the equivalent way of doing it?

View detail

Migrating from ASP.NET Core 1.x to ASP.NET Core 2.0

882 views   2 comments last modified about 10 months ago

Migrating from ASP.NET Core 1.x to 2.0 is not an easy job especially if you have customized Identity and used customized authentication. This post summarizes the issues and errors I have experienced and their resolutions when upgrading my project. Hopefully it can save you sometime if you are doi...

View detail

OpenIddict Refresh Token Flow issue ASP.NET Core 2.0

1022 views   0 comments last modified about 10 months ago

Context When I followed OpenIDDict refresh flow sample, I constantly got the issue “The refresh token is no longer valid”, which is returned by the following code in my authorization web api controller: result.Content = new OpenIdConnectResponse &nbsp;&...

View detail

Add comment

Please login first to add comments.  Log in New user?  Register

Comments (2)

R Re:Migrate from Universal Membership Provider to ASP.NET Identity 2.1.0 - PasswordHarsher

*** about 4 years ago

HashPassword method can be constomed based on your previous settings.
R Re:Migrate from Universal Membership Provider to ASP.NET Identity 2.1.0 - PasswordHarsher

Ra*** about 4 years ago

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.