Curiously I was inspecting the ASP.NET Core identity PasswordHasher Code and I have found that it is taking TUser as the generic argument but TUser is not being used anywhere inside the code as follows:
public interface IPasswordHasher<TUser> where TUser : class
{
string HashPassword(TUser user, string password);
PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword);
}
public class PasswordHasher<TUser> : IPasswordHasher<TUser> where TUser : class
{
private readonly PasswordHasherCompatibilityMode _compatibilityMode;
private readonly int _iterCount;
private readonly RandomNumberGenerator _rng;
public PasswordHasher(IOptions<PasswordHasherOptions> optionsAccessor = null)
{
// Code omitted for brevity
}
#if NETSTANDARD2_0
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
// Code omitted for brevity
}
#endif
public virtual string HashPassword(TUser user, string password)
{
if (password == null)
{
throw new ArgumentNullException(nameof(password));
}
if (_compatibilityMode == PasswordHasherCompatibilityMode.IdentityV2)
{
return Convert.ToBase64String(HashPasswordV2(password, _rng));
}
else
{
return Convert.ToBase64String(HashPasswordV3(password, _rng));
}
}
private static byte[] HashPasswordV2(string password, RandomNumberGenerator rng)
{
// Code omitted for brevity
}
private byte[] HashPasswordV3(string password, RandomNumberGenerator rng)
{
// Code omitted for brevity
}
private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
{
// Code omitted for brevity
}
private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
// Code omitted for brevity
}
public virtual PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword)
{
if (hashedPassword == null)
{
throw new ArgumentNullException(nameof(hashedPassword));
}
if (providedPassword == null)
{
throw new ArgumentNullException(nameof(providedPassword));
}
byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword);
// read the format marker from the hashed password
if (decodedHashedPassword.Length == 0)
{
return PasswordVerificationResult.Failed;
}
switch (decodedHashedPassword[0])
{
case 0x00:
if (VerifyHashedPasswordV2(decodedHashedPassword, providedPassword))
{
// This is an old password hash format - the caller needs to rehash if we're not running in an older compat mode.
return (_compatibilityMode == PasswordHasherCompatibilityMode.IdentityV3)
? PasswordVerificationResult.SuccessRehashNeeded
: PasswordVerificationResult.Success;
}
else
{
return PasswordVerificationResult.Failed;
}
case 0x01:
int embeddedIterCount;
if (VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, out embeddedIterCount))
{
// If this hasher was configured with a higher iteration count, change the entry now.
return (embeddedIterCount < _iterCount)
? PasswordVerificationResult.SuccessRehashNeeded
: PasswordVerificationResult.Success;
}
else
{
return PasswordVerificationResult.Failed;
}
default:
return PasswordVerificationResult.Failed; // unknown format marker
}
}
private static bool VerifyHashedPasswordV2(byte[] hashedPassword, string password)
{
// Code omitted for brevity
}
private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, out int iterCount)
{
// Code omitted for brevity
}
private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
// Code omitted for brevity
}
}
If you closely look at the HashPassword and VerifyHashedPassword methods, both are taking TUser as an argument but it is not being used anywhere inside both methods.
Now my question is what is the purpose of making IPasswordHasher<TUser> and PasswordHasher<TUser> as generic instead of plain IPasswordHasher and PasswordHasher while there is no use of TUser inside the context?
It allows you to use information about the target user as part of the password hashing algorithm. Some applications use this as an extra layer of defense if they're particularly concerned about the consequences of their membership database being leaked. For example, some applications might opt to compute HMAC(hKey, user_identifier), where _hKey_ is stored within an HSM, then combine the resulting digest with the per-user salt as part of the PBKDF routine. This algorithm would prevent somebody who downloads a copy of the membership database from mounting an offline attack.
Using per-user data in this fashion as part of the KDF isn't a very common scenario.
@GrabYourPitchforks You are saying that if I override the HashPassword method and write my own implementation then I can use HMAC(hKey, user_identifier) for more security, am I correct? But in the default implementation, there is no usage of Tuser, Did I get things right?
@TanvirArjel the "more security" would largely come down to how _hKey_ is stored - preferably within an HSM and marked non-exportable - but yes, that's the gist.
Have same question here. Actually my concern comes from whether or not the passed in user object will have effect on password hashing, if so, further change to the class will possibly invalidate the saved password(I do not believe this is so dumb but just to ensure...).
However after going through the source code, the user class is not used at all...
Expecting response from @blowdart too!.
Expecting response from @blowdart too!.
@TanvirArjel but I guess the purpose is clear now, they provide the interface for others to build some logic that is related to the user object
@Meowzz95 This should have been in a different interface as most of the people use the default implementation as sufficient where TUser is not being used but forced to pass as an argument. So it's somewhat misleading.
As @GrabYourPitchforks is .NET's princple security dev you really don't need a response from me if you have one from him.
Closing as answered.
Most helpful comment
As @GrabYourPitchforks is .NET's princple security dev you really don't need a response from me if you have one from him.