密码(哈希)不匹配,当重新使用现有的Microsoft Identity用户表时。

8
我们有一个现有的SQL数据库,其中包含由ASP.NET Core应用程序生成的Microsoft Identity表。
我们还有一个使用Microsoft Identity的ASP.NET 4应用程序。
我们希望ASP.NET 4应用程序能够使用与原始.NET Core应用程序相同的数据库验证登录。
然而,当我们尝试验证密码时,它们不匹配。
我猜测由.NET Core应用程序生成的密码哈希无法被ASP.NET 4应用程序验证,但是我不确定该从哪里开始。 :)
在.NET Core应用程序中没有自定义密码哈希,并且我正在努力查找可能影响哈希的任何配置?
非常感谢您的任何帮助或指针!
编辑:似乎这可能是Identity V2 / V3中不同哈希算法引起的。不确定如何在ASP.NET 4应用程序中模仿V3哈希算法。
2个回答

6
根据位于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.)
     */

曾经,身份验证使用了不同的哈希算法 - 可能在某个时候它在其中一个版本中使用了版本2格式,在另一个版本中使用了版本3格式?

该类的构造函数接受选项参数,您可以尝试调整以获取正确的哈希值?

public PasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)

编辑:

我在这里找到了2.0版的身份源代码:https://aspnetidentity.codeplex.com/ 和 git存储库: https://git01.codeplex.com/aspnetidentity

查看源代码时,您会发现其哈希方法。

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);
    }

与 aspnet identity core 中的 v2 相比:

    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;
    }

身份验证 v2 哈希和身份验证核心 v2 哈希看起来非常相似,现在与身份验证核心 v3 哈希进行比较:

    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;
    }

我不会假装理解这些方法的具体实现,但是从身份验证v2和身份验证核心来看,我们从无参数构造函数转换为带有配置选项的构造函数。V2使用SHA1,V3使用SHA256(等等其他方法)。
似乎身份验证核心默认使用V3方法进行哈希,而在旧版本的身份验证中不存在该方法 - 这可能是您问题的原因。

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

在上述源代码中,V3被用作默认值。
    /// <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;

很不幸,这意味着在 identity core 中哈希的密码无法在旧版本的 identity 中使用相同的哈希方法进行哈希,因为旧方法没有被实现。也许您可以创建自己的方法来模拟 v3 中所做的操作?

奇怪的是,PasswordHasher类似乎只有一个无参构造函数。但版本差异可能会解释这一点,Core应用程序在Identity 3上,而ASP.NET 4应用程序在Identity 2上。不确定V3是否适用于.NET 4.6.1? - Ted Nyberg
1
我目前正在研究将V3哈希算法移植到ASP.NET 4应用程序中。否则,我想Core应用程序可以改为使用V2哈希,但我真的更喜欢使用V3(基本上是将当前的V3哈希迁移到V2哈希)。非常感谢您的帮助! - Ted Nyberg

4

补充Kritner的优秀答案并简化:

如果你从Microsoft.AspNet.Identity.Core迁移到Microsoft.AspNetCore.Identity(请注意微妙的区别),你需要替换

// Microsoft.AspNet.Identity.Core
PasswordHasher hasher = new PasswordHasher(); 

使用看起来像某些东西的方式

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

// Microsoft.AspNetCore.Identity
PasswordHasher<YourUserTypeActuallyImmaterial> hasher 
= new PasswordHasher<YourUserTypeActuallyImmaterial>(
                Options.Create(new PasswordHasherOptions()
                {
                    CompatibilityMode = PasswordHasherCompatibilityMode.IdentityV2,
                }));

此后,API 的区别在于新的 PasswordHasher<T> 在哈希或验证时需要传入一个 T 实例。

这将以与旧方法兼容的方式哈希纯文本,并成功地验证使用先前方法哈希的有效密码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接