What are the best practices for storing passwords?
4 minintermediatesecuritypasswordshashingauthentication
Quick Answer
Never store passwords in plain text or with reversible encryption. Store a salted, slow cryptographic hash using a purpose-built algorithm (bcrypt, scrypt, Argon2, or PBKDF2), with a unique random salt per password and a high work factor. In .NET, prefer ASP.NET Core Identity, which handles hashing, salting, and verification correctly. Enforce strong passwords and consider MFA.
Detailed Answer
Never store passwords in plain text! Always use cryptographic hashing with salting.
Best Practices in .NET Core:
- Use ASP.NET Core Identity (Recommended):
// Startup.cs
services.AddIdentity(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 12;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
})
.AddEntityFrameworkStores();
// Usage
public class AccountController : Controller
{
private readonly UserManager _userManager;
public async Task Register(RegisterModel model)
{
var user = new ApplicationUser { UserName = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
}
}
- Manual Implementation with BCrypt:
using BCrypt.Net;
public class PasswordHasher
{
public string HashPassword(string password)
{
return BCrypt.HashPassword(password, BCrypt.GenerateSalt(12));
}
public bool VerifyPassword(string password, string hashedPassword)
{
return BCrypt.Verify(password, hashedPassword);
}
}
- Using PBKDF2 (Built-in .NET):
using System.Security.Cryptography;
public class PasswordHasher
{
private const int SaltSize = 16;
private const int HashSize = 20;
private const int Iterations = 100000;
public string HashPassword(string password)
{
byte[] salt = new byte[SaltSize];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256);
byte[] hash = pbkdf2.GetBytes(HashSize);
byte[] hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
return Convert.ToBase64String(hashBytes);
}
public bool VerifyPassword(string password, string hashedPassword)
{
byte[] hashBytes = Convert.FromBase64String(hashedPassword);
byte[] salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, Iterations, HashAlgorithmName.SHA256);
byte[] hash = pbkdf2.GetBytes(HashSize);
for (int i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
return false;
}
return true;
}
}
Key Principles:
- Use adaptive hashing algorithms (BCrypt, Argon2, PBKDF2)
- Always use unique salts per password
- Use sufficient iteration counts (work factor)
- Never store passwords reversibly