8

I have sitecore 9.1 and I added pipeline which add new hashing algorithm (pbkdf2 implementation from Zetetic team with 5000 iterations) to CryptoConfig and changed hashAlgorithmType to pbkdf2 in membership section of web.config as well. Changing of passwords from sitecore works well, and logging in with sitecore login works as well (sitecore-instance.sc/sitecore/login?rc=1), but it looks like Identity Server uses pbkdf2 with 10000 iterations (default settings for IdentityServer 3+), because login is failed with it (sitecore-instance.identityserver/account/login).

UPDATE: Actually, sitecore implementation uses identityserver-contrib-membership to work with membership provider under the hood, which has hardcoded implementation of SHA-1 algorithm - MembershipPasswordHasher. As I saw in documentation Sitecore provides possibility to replace class implementation with custom one for Sitecore Host plugins (Host plugin Configuration doc) , but I didn't find good example of this configuration.

So, how to configure identity server to use same algorithm as Membership provider from sitecore instance?

3
  • Does this help grantkillian.wordpress.com/2019/01/02/… ?
    – Marek Musielak
    Commented Apr 8, 2019 at 14:02
  • Actually not, it patches app.settings, but sitecore 9.1 doesn't have any app.settings file in project structure. It uses compiled libraries. I will try to decompile sitecore libraries to figure out how to override this configuration
    – Mickey P
    Commented Apr 8, 2019 at 14:18
  • 1
    For SHA512 there s a bug with public reference number 302092 and a hotfix available for Sitecore Identity Server 2.0.0 which supports SHA512. Contact Sitecore Support for the hotfix
    – andyp
    Commented Jun 20, 2019 at 8:06

1 Answer 1

2

You need to create a new Identity Server plugin.

I used Sitecore.Plugin.Authentication.SameSite as an example. Duplicate the folder sitecore\Sitecore.Plugin.Authentication.SameSite and give it a new name such as Sitecore.Plugin.IdentityServer.Custom.

Create a new project in VS using netstandard2.0 as the target framework:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.14">
          <PrivateAssets>all</PrivateAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.14">
          <PrivateAssets>all</PrivateAssets>
        </PackageReference>
    </ItemGroup>

  <ItemGroup>
    <Reference Include="IdentityServer4.Contrib.Membership">
      <HintPath>IdentityServer4.Contrib.Membership.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <None Update="IdentityServer4.Contrib.Membership.dll">
      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

Reference the OOTB IdentityServer4.Contrib.Membership.dll as shown above.

Create a new class ConfigurableMembershipPasswordHasher

public class ConfigurableMembershipPasswordHasher : IMembershipPasswordHasher
{
    private readonly string _hashAlgorithm;

    public ConfigurableMembershipPasswordHasher(string hashAlgorithm)
    {
        _hashAlgorithm = ((!string.IsNullOrEmpty(hashAlgorithm)) ? hashAlgorithm : "SHA256");
    }

    /// <summary>Encrypts a password using the given format and salt</summary>
    /// <param name="password">The password to encrypt / hash</param>
    /// <param name="passwordFormat">The format to use 0 = clear, 1 = encrypt</param>
    /// <param name="salt">The salt to apply to the password</param>
    /// <returns>The encrypted / hashed password</returns>
    /// <remarks>Taken from the original Membership code</remarks>
    public string EncryptPassword(string password, int passwordFormat, string salt)
    {
        switch (passwordFormat)
        {
            case 0:
                return password;
            case 1:
                return GetPasswordHash(password, salt);
            default:
                throw new InvalidOperationException($"Unsupported Password Format '{passwordFormat}'");
        }
    }

    private string GetPasswordHash(string password, string salt)
    {
        byte[] bytes = Encoding.Unicode.GetBytes(password);
        byte[] array = Convert.FromBase64String(salt);
        HashAlgorithm hashAlgorithm = GetHashAlgorithm();
        byte[] inArray;
        if (hashAlgorithm is KeyedHashAlgorithm keyedHashAlgorithm)
        {
            if (keyedHashAlgorithm.Key.Length == array.Length)
            {
                keyedHashAlgorithm.Key = array;
            }
            else if (keyedHashAlgorithm.Key.Length < array.Length)
            {
                byte[] array2 = new byte[keyedHashAlgorithm.Key.Length];
                Buffer.BlockCopy(array, 0, array2, 0, array2.Length);
                keyedHashAlgorithm.Key = array2;
            }
            else
            {
                byte[] array3 = new byte[keyedHashAlgorithm.Key.Length];
                int num;
                for (int i = 0; i < array3.Length; i += num)
                {
                    num = Math.Min(array.Length, array3.Length - i);
                    Buffer.BlockCopy(array, 0, array3, i, num);
                }
                keyedHashAlgorithm.Key = array3;
            }
            inArray = keyedHashAlgorithm.ComputeHash(bytes);
        }
        else
        {
            byte[] array4 = new byte[array.Length + bytes.Length];
            Buffer.BlockCopy(array, 0, array4, 0, array.Length);
            Buffer.BlockCopy(bytes, 0, array4, array.Length, bytes.Length);
            inArray = hashAlgorithm.ComputeHash(array4);
        }
        return Convert.ToBase64String(inArray);
    }

    private HashAlgorithm GetHashAlgorithm()
    {
        string[] pBKDF2Hash = new string[] { "PBKDF2Hash" };
        string[] source = new string[3] { "SHA", "SHA1", "SHA1Cng" };
        string[] source2 = new string[3] { "SHA256", "SHA-256", "SHA256Cng" };
        string[] source3 = new string[3] { "SHA384", "SHA-384", "SHA384Cng" };
        string[] source4 = new string[3] { "SHA512", "SHA-512", "SHA512Cng" };
        string[] source5 = new string[4] { "HMACSHA1", "HMACSHA256", "HMACSHA384", "HMACSHA512" };
        if (pBKDF2Hash.Contains<string>(_hashAlgorithm, StringComparer.OrdinalIgnoreCase))
        {
            return PBKDF2Hash.Create();
        }
        if (source.Contains<string>(_hashAlgorithm, StringComparer.OrdinalIgnoreCase))
        {
            return SHA1.Create();
        }
        if (source2.Contains<string>(_hashAlgorithm, StringComparer.OrdinalIgnoreCase))
        {
            return SHA256.Create();
        }
        if (source3.Contains<string>(_hashAlgorithm, StringComparer.OrdinalIgnoreCase))
        {
            return SHA384.Create();
        }
        if (source4.Contains<string>(_hashAlgorithm, StringComparer.OrdinalIgnoreCase))
        {
            return SHA512.Create();
        }
        if (source5.Contains<string>(_hashAlgorithm, StringComparer.OrdinalIgnoreCase))
        {
            return HMAC.Create(_hashAlgorithm);
        }
        throw new InvalidOperationException("Unsupported Password Hash Algorithm '" + _hashAlgorithm + "'");
    }
}

Create a new class

public sealed class ConfigureSitecore
{

    public void ConfigureServices(IServiceCollection services)
    {
        services.Replace(new ServiceDescriptor(typeof(IMembershipPasswordHasher), (IServiceProvider p) => new ConfigurableMembershipPasswordHasher("PBKDF2Hash"), ServiceLifetime.Transient));
    }
}

Build and copy the DLL's and config to identity server, then restart the server. It should be using the new hashing algorithm.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.