我有一个应用程序,我最初是在我年轻和愚蠢时构建的,它的身份验证是使用SimpleMembership框架设置的,所有用户数据都包含在webpages_Membership
表中。我非常有兴趣通过SQL Server将我的后端重建为具有AspNetCore Identity的AspNetCore Web API,而不会丢失用户信息。
我很幸运能够提出SQL脚本将所有内容移到AspNetUsers
表中,以准备使用Identity而不是SimpleMembership,但我遇到的问题是密码哈希。我从this和this这样的文章中收集到,我最好的选择是覆盖PasswordHasher<IdentityUser>
以复制SimpleMembership加密流,然后在密码进入时逐步迁移数据库。 / p>
问题是我无法找到如何在.NET Core中实现此流复制。上面链接的后一篇文章指出,SimpleMembership流程是通过System.Web.Helpers.Crypto
包实现的,这个包似乎不存在于.NET Core库中,我无法弄清楚它的实现是否记录在任何地方。 (Its MSDN documentation表示它正在使用RFC2898哈希,但我对加密知之甚少,不知道它是否足以让它自行继续。这不是我的专业领域。 :()
非常感谢任何有关如何处理此问题的见解。谢谢!
答案 0 :(得分:2)
对于可能遇到同样问题的其他人 - 我能够在GitHub上的某个地方找到System.Web.Helpers.Crypto
代码的副本,并且或多或少地将其复制到自定义密码hasher类中:
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;
}
}
此类重写VerifyHashedPassword
并检查用户提供的密码是否与旧的加密哈希一起使用;如果是,则该方法返回PasswordVerificationResult.SuccessRehashNeeded
。否则,它会将密码传递给基类的方法,并使用.NET Core散列行为将其验证为正常。
然后,您可以指示UserManager
使用此密码hasher而不是默认值,方法是将其包含在Startup.cs
中的依赖项注入配置中:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();
}
...
}
我最终的目的是让我的控制器在返回SuccessRehashNeeded
结果时触发用户密码的重新搜索,从而允许所有用户逐渐迁移到正确的散列模式。