如何将SimpleMembership用户数据迁移到ASPNET Core Identity

ros*_*lls 6 authentication cryptography asp.net-identity .net-core asp.net-core

我有一个应用程序,该应用程序最初是在我年轻而愚蠢的时候构建的,它的身份验证是使用SimpleMembership框架设置的,所有用户数据都包含在webpages_Membership表中。我非常有兴趣通过SQL Server使用AspNetCore身份将后端重建为AspNetCore Web API,而又不会丢失用户信息。

运气很好,我想出了SQL脚本将所有内容移到AspNetUsers表中,以准备使用Identity而不是SimpleMembership,但是遇到问题的是密码哈希。我从喜欢的文章收集这个这个,我的最好的办法是重写PasswordHasher<IdentityUser>复制的SimpleMembership加密流量,然后再老调重弹的密码,因为他们进来逐步迁移数据库。

问题是我无法找到如何在.NET Core中实现这种流复制。上面链接的后一篇文章指出,SimpleMembership流是通过System.Web.Helpers.Crypto包实现的,该包在.NET Core库中似乎不存在,并且我不知道它的实现是否在任何地方都有文档记录。(它的MSDN文档说它正在使用RFC2898哈希,但是我对加密还不了解,不足以了解加密本身是否足够进行。这不是我的专业领域。:()

任何有关如何解决此问题的见解将不胜感激。谢谢!

ros*_*lls 5

对于可能遇到同样麻烦的其他人 - 我能够System.Web.Helpers.Crypto在 GitHub 上的某个地方找到代码的副本,并或多或少地将其复制到自定义密码哈希器类中,如下所示:

public class CustomPasswordHasher : PasswordHasher<IdentityUser>
{
    public override PasswordVerificationResult VerifyHashedPassword(IdentityUser user, string hashedPassword,
        string providedPassword)
    {
        var isValidPasswordWithLegacyHash = VerifyHashedPassword(hashedPassword, providedPassword);
        return isValidPasswordWithLegacyHash
            ? PasswordVerificationResult.SuccessRehashNeeded
            : base.VerifyHashedPassword(user, hashedPassword, providedPassword);
    }

    private const int _pbkdf2IterCount = 1000;
    private const int _pbkdf2SubkeyLength = 256 / 8;
    private const int _saltSize = 128 / 8;

    public static bool VerifyHashedPassword(string hashedPassword, string password)
    {
        //Checks password using legacy hashing from System.Web.Helpers.Crypto
        var hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
        if (hashedPasswordBytes.Length != (1 + _saltSize + _pbkdf2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
        {
            return false;
        }
        var salt = new byte[_saltSize];
        Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, _saltSize);
        var storedSubkey = new byte[_pbkdf2SubkeyLength];
        Buffer.BlockCopy(hashedPasswordBytes, 1 + _saltSize, storedSubkey, 0, _pbkdf2SubkeyLength);
        byte[] generatedSubkey;
        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, _pbkdf2IterCount))
        {
            generatedSubkey = deriveBytes.GetBytes(_pbkdf2SubkeyLength);
        }
        return ByteArraysEqual(storedSubkey, generatedSubkey);
    }

    internal static string BinaryToHex(byte[] data)
    {
        var hex = new char[data.Length * 2];
        for (var iter = 0; iter < data.Length; iter++)
        {
            var hexChar = (byte) (data[iter] >> 4);
            hex[iter * 2] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
            hexChar = (byte) (data[iter] & 0xF);
            hex[iter * 2 + 1] = (char) (hexChar > 9 ? hexChar + 0x37 : hexChar + 0x30);
        }
        return new string(hex);
    }

    [MethodImpl(MethodImplOptions.NoOptimization)]
    private static bool ByteArraysEqual(byte[] a, byte[] b)
    {
        if (ReferenceEquals(a, b))
        {
            return true;
        }
        if (a == null || b == null || a.Length != b.Length)
        {
            return false;
        }
        var areSame = true;
        for (var i = 0; i < a.Length; i++)
        {
            areSame &= (a[i] == b[i]);
        }
        return areSame;
    }
}
Run Code Online (Sandbox Code Playgroud)

此类覆盖VerifyHashedPassword并检查用户提供的密码是否适用于旧的加密哈希;如果是,该方法返回PasswordVerificationResult.SuccessRehashNeeded。否则,它将密码传递给基类的方法,并使用 .NET Core 哈希行为正常验证它。

然后,您可以通过将其包含在依赖项注入配置中来指示您UserManager使用此密码哈希器而不是默认值Startup.cs

public class Startup 
{
...
    public void ConfigureServices(IServiceCollection services)
    {
    ...
        services.AddScoped<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();
    }
...
} 
Run Code Online (Sandbox Code Playgroud)

我的最终目的是让我的控制器在返回结果时触发用户密码的重新哈希SuccessRehashNeeded,从而允许所有用户逐渐迁移到正确的哈希模式。