Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Password (hash) doesn't match when re-using existing Microsoft Identity user tables

We have an existing SQL database with Microsoft Identity tables, originally generated by a ASP.NET Core app.

We also have an ASP.NET 4 app, which also uses Microsoft Identity.

We'd like the ASP.NET 4 app to be able to validate logins using the same database as the original .NET Core app.

However, when we try to validate passwords, they don't match.

I'm just guessing that the password hashes generated by the .NET Core app cannot be validated by the ASP.NET 4 app, but I'm not sure where to go from here. :)

There's no custom password hashing in the .NET Core app, and I'm struggling to find any config that might affect hashing?

Any help or pointer is greatly appreciated!

Edit: It seems this may be caused by different hashing algorithms in Identity V2/V3. Not sure how to mimic the V3 hashing algorithm in the ASP.NET 4 app, though.

like image 492
Ted Nyberg Avatar asked Jul 06 '16 15:07

Ted Nyberg


1 Answers

As per the documentation located: https://github.com/aspnet/Identity/blob/a8ba99bc5b11c5c48fc31b9b0532c0d6791efdc8/src/Microsoft.AspNetCore.Identity/PasswordHasher.cs

    /* =======================
     * HASHED PASSWORD FORMATS
     * =======================
     * 
     * Version 2:
     * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
     * (See also: SDL crypto guidelines v5.1, Part III)
     * Format: { 0x00, salt, subkey }
     *
     * Version 3:
     * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
     * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
     * (All UInt32s are stored big-endian.)
     */

At one point, identity used a different hashing algorithm - maybe it's using the version 2 format in one, and the version 3 format in the other?

The constructor of the class takes in options, you can try tweaking that to get the correct hash?

public PasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)

EDIT:

I found the Identity v2.0 source here: https://aspnetidentity.codeplex.com/ and git repo: https://git01.codeplex.com/aspnetidentity

Looking through source, you come across its hashing method.

Crypto.HashPassword.cs

public static string HashPassword(string password)
    {
        if (password == null)
        {
            throw new ArgumentNullException("password");
        }

        // Produce a version 0 (see comment above) text hash.
        byte[] salt;
        byte[] subkey;
        using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount))
        {
            salt = deriveBytes.Salt;
            subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
        }

        var outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength];
        Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
        Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength);
        return Convert.ToBase64String(outputBytes);
    }

Compared to v2 in aspnet identity core:

    private static byte[] HashPasswordV2(string password, RandomNumberGenerator rng)
    {
        const KeyDerivationPrf Pbkdf2Prf = KeyDerivationPrf.HMACSHA1; // default for Rfc2898DeriveBytes
        const int Pbkdf2IterCount = 1000; // default for Rfc2898DeriveBytes
        const int Pbkdf2SubkeyLength = 256 / 8; // 256 bits
        const int SaltSize = 128 / 8; // 128 bits

        // Produce a version 2 (see comment above) text hash.
        byte[] salt = new byte[SaltSize];
        rng.GetBytes(salt);
        byte[] subkey = KeyDerivation.Pbkdf2(password, salt, Pbkdf2Prf, Pbkdf2IterCount, Pbkdf2SubkeyLength);

        var outputBytes = new byte[1 + SaltSize + Pbkdf2SubkeyLength];
        outputBytes[0] = 0x00; // format marker
        Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
        Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, Pbkdf2SubkeyLength);
        return outputBytes;
    }

Identity v2 hashing and identity core v2 hashing seem pretty similar, now compared to identity core v3 hash:

    private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
    {
        // Produce a version 3 (see comment above) text hash.
        byte[] salt = new byte[saltSize];
        rng.GetBytes(salt);
        byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

        var outputBytes = new byte[13 + salt.Length + subkey.Length];
        outputBytes[0] = 0x01; // format marker
        WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
        WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
        WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
        Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
        Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
        return outputBytes;
    }

I'm not going to pretend to understand what's going on in these methods, but from identity v2, and identity core, we went from a parameterless constructor to one that takes in configuration options. V2 uses SHA1, V3 uses SHA256 (among other things).

It looks like identity core by default would hash using V3 method, which did not exist in the older version of identity - which would be the cause of your problem.

https://github.com/aspnet/Identity/blob/a8ba99bc5b11c5c48fc31b9b0532c0d6791efdc8/src/Microsoft.AspNetCore.Identity/PasswordHasherOptions.cs

Note in the above source, V3 is used as the default.

    /// <summary>
    /// Gets or sets the compatibility mode used when hashing passwords.
    /// </summary>
    /// <value>
    /// The compatibility mode used when hashing passwords.
    /// </value>
    /// <remarks>
    /// The default compatibility mode is 'ASP.NET Identity version 3'.
    /// </remarks>
    public PasswordHasherCompatibilityMode CompatibilityMode { get; set; } = PasswordHasherCompatibilityMode.IdentityV3;

Unfortunately, that looks like it means your passwords that were hashed in identity core cannot be hashed the same in an older version of identity, as that older method was not implemented. Perhaps you could create your own mimicking what was done in v3?

like image 165
Kritner Avatar answered Nov 01 '22 16:11

Kritner