The Problem: Legacy Password Migration
When migrating an existing application to ASP.NET Core Identity, you often face a critical challenge: your users' passwords are hashed using a legacy algorithm that differs from Identity's default password hasher.
You can't simply re-hash the passwords because:
• You don't have access to the plaintext passwords (and shouldn't!)
• Forcing all users to reset their passwords creates a poor user experience
• Mass password resets can trigger security concerns among users
The Solution: A Fallback Password Hasher
The FallbackPasswordHasher provides an elegant solution by supporting both legacy and modern password hashing schemes simultaneously.
How It Works
The hasher follows a simple but powerful pattern:
public override PasswordVerificationResult VerifyHashedPassword(
TUser user, string hashedPassword, string providedPassword)
{
// 1. Try to detect legacy format (pipe-delimited: hash|?|salt)
string[] passwordProperties = hashedPassword.Split('|');
if (passwordProperties.Length != 3)
{
// Modern format - use standard ASP.NET Core Identity hashing
return base.VerifyHashedPassword(user, hashedPassword, providedPassword);
}
// 2. Legacy format detected - verify using old algorithm
string passwordHash = passwordProperties[0];
string salt = passwordProperties[2];
if (String.Equals(EncryptPassword(providedPassword, salt), passwordHash,
StringComparison.CurrentCultureIgnoreCase))
{
// Success! Signal that password should be rehashed
return PasswordVerificationResult.SuccessRehashNeeded;
}
return PasswordVerificationResult.Failed;
}PasswordVerificationResult
The Magic: SuccessRehashNeeded
The key to seamless migration is returning PasswordVerificationResult.SuccessRehashNeeded. This tells ASP.NET Core Identity:
The password is correct - let the user in
The password needs to be rehashed using the modern algorithm
Save the new hash on the next password update
This means users are automatically migrated from legacy to modern hashing without any action required on their part.
Understanding the Legacy Algorithm
Many older ASP.NET systems used HMACSHA512 with custom salt handling. The EncryptPassword(string, string) method reconstructs this legacy process:
private string EncryptPassword(string pass, string salt)
{
var bPassword = Encoding.Unicode.GetBytes(pass);
var bSalt = Convert.FromBase64String(salt);
using var hashAlgorithm = HashAlgorithm.Create("HMACSHA512");
if (hashAlgorithm is KeyedHashAlgorithm keyedHashAlgorithm)
{
// Adjust the key to match the salt length
keyedHashAlgorithm.Key = AdjustKeyToSaltLength(bSalt,
keyedHashAlgorithm.Key.Length);
hash = keyedHashAlgorithm.ComputeHash(bPassword);
}
return Convert.ToBase64String(hash);
}EncryptPassword
The Key Adjustment Challenge
One subtle complexity is that HMACSHA512 has a fixed key length (128 bytes), but legacy salts might be any length. The AdjustKeyToSaltLength(byte[], int) method handles three scenarios:
Exact match: Use the salt as-is
Salt too long: Truncate to the required length
Salt too short: Repeat the salt until it fills the key
private static byte[] AdjustKeyToSaltLength(byte[] salt, int requiredKeyLength)
{
if (salt.Length == requiredKeyLength)
return salt;
var adjustedKey = new byte[requiredKeyLength];
if (requiredKeyLength < salt.Length)
{
Buffer.BlockCopy(salt, 0, adjustedKey, 0, requiredKeyLength);
}
else
{
// Repeat salt to fill the required key length
for (var i = 0; i < requiredKeyLength;)
{
var len = Math.Min(salt.Length, requiredKeyLength - i);
Buffer.BlockCopy(salt, 0, adjustedKey, i, len);
i += len;
}
}
return adjustedKey;
}AdjustKeyToSaltLength
Implementation Benefits
Zero Downtime Migration
Users continue logging in normally during and after the migration. No maintenance windows required.
Gradual Security Improvement
As users log in, they're automatically upgraded to the more secure ASP.NET Core Identity password hashing algorithm (PBKDF2 with HMAC-SHA256 by default).
Backward Compatibility
The system continues to support legacy hashes indefinitely for users who don't log in frequently.
Fail-Safe Design
If the legacy format isn't detected, the hasher falls back to the standard Identity implementation, maintaining forward compatibility.
When Should You Use This Pattern?
Consider implementing a fallback password hasher when:
Migrating from legacy .NET Framework applications to .NET Core
Adopting ASP.NET Core Identity in an existing system
Upgrading from custom authentication to Identity
Consolidating multiple systems with different hashing schemes
Security Considerations
While this pattern is practical, keep in mind:
Legacy algorithms may be weaker than modern standards. Monitor and eventually deprecate support for the legacy format.
Document the migration timeline. Consider forcing password resets for users who haven't logged in after a reasonable period (e.g., 6-12 months).
The modern Identity hasher uses PBKDF2 with adaptive iteration counts, making it much more resistant to brute-force attacks.
Conclusion
The Fallback Password Hasher pattern demonstrates how thoughtful software design can bridge the gap between legacy systems and modern security practices. By supporting both old and new hashing schemes simultaneously, you can migrate users seamlessly while improving security over time.
This approach respects your users' experience while maintaining the highest security standards for new authentications a win-win for everyone involved.