我们现有一个带有 Microsoft Identity 表的SQL数据库,最初由ASP.NET Core应用程序生成。
我们还有一个ASP.NET 4应用程序,它也使用Microsoft Identity。
我们希望ASP.NET 4应用能够使用与原始.NET Core应用相同的数据库验证登录。
但是,当我们尝试验证密码时,它们并不匹配。
我只是猜测.NET 4应用程序生成的密码哈希值无法通过ASP.NET 4应用程序验证,但我不确定从何处开始。 :)
.NET Core应用程序中没有自定义密码哈希,我很难找到任何可能影响哈希的配置?
非常感谢任何帮助或指针!
编辑:这似乎可能是由身份V2 / V3中的不同哈希算法引起的。但不知道如何模仿ASP.NET 4应用程序中的V3散列算法。
答案 0 :(得分:5)
/* =======================
* 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)
编辑:
我在此处找到了Identity v2.0源:https://aspnetidentity.codeplex.com/和git repo: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身份核心中的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;
}
Identity 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方法进行哈希处理,这在旧版本的身份中不存在 - 这可能是导致问题的原因。
请注意,在上面的源代码中,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;
不幸的是,这看起来意味着您在身份核心中散列的密码不能在较旧版本的身份中进行散列,因为旧方法未实现。也许你可以创建自己的模仿v3中的内容?
答案 1 :(得分:1)
要使用TLDR跟进Kritner的出色回答:
TLDR,如果您要从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
的实例。
这将以与旧方法兼容的方式对纯文本进行哈希处理,并将成功验证由先前方法哈希处理的有效密码。