Skip to main content

Overview

AuthService uses PBKDF2-SHA512 with 600,000 iterations for password hashing, following NIST SP 800-132 recommendations and OWASP 2024 guidelines for password storage.
PBKDF2 (Password-Based Key Derivation Function 2) is a key derivation function that applies a pseudorandom function to the input password along with a salt value and repeats the process many times to produce a derived key.

Implementation Details

Hash Parameters

The password service uses the following cryptographic parameters:
Services/PasswordService.cs
private const int SaltSize = 32;       // 256 bits
private const int HashSize = 64;       // 512 bits
private const int Iterations = 600_000; // OWASP 2024 recommendation
private static readonly HashAlgorithmName Algorithm = HashAlgorithmName.SHA512;
ParameterValueRationale
Salt Size32 bytes (256 bits)Ensures each password hash is unique, prevents rainbow table attacks
Hash Size64 bytes (512 bits)Matches SHA-512 output size for maximum security
Iterations600,000OWASP 2024 recommendation for PBKDF2-HMAC-SHA512, balances security and performance
AlgorithmSHA-512More secure than SHA-256, widely supported in .NET

Hashing Process

When a user registers or changes their password, the service generates a cryptographically secure hash:
Services/PasswordService.cs
public string Hash(string password)
{
    var salt = RandomNumberGenerator.GetBytes(SaltSize);
    var hash = Rfc2898DeriveBytes.Pbkdf2(
        Encoding.UTF8.GetBytes(password),
        salt,
        Iterations,
        Algorithm,
        HashSize
    );

    // Formato: iterations:salt_base64:hash_base64
    return $"{Iterations}:{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}";
}
The hash format stores three components separated by colons:
  1. Iteration count - Allows for future upgrades (e.g., increasing from 600k to 1M)
  2. Salt (Base64) - The random salt used for this specific password
  3. Hash (Base64) - The derived key from PBKDF2
Storing the iteration count in the hash string allows the system to gracefully upgrade security parameters over time. When a user logs in, the system can detect if their password was hashed with older parameters and rehash it with current standards.

Password Verification

During login, the service verifies passwords using constant-time comparison:
Services/PasswordService.cs
public bool Verify(string password, string storedHash)
{
    var parts = storedHash.Split(':');
    if (parts.Length != 3) return false;

    if (!int.TryParse(parts[0], out var iterations)) return false;

    var salt = Convert.FromBase64String(parts[1]);
    var expectedHash = Convert.FromBase64String(parts[2]);

    var actualHash = Rfc2898DeriveBytes.Pbkdf2(
        Encoding.UTF8.GetBytes(password),
        salt,
        iterations,
        Algorithm,
        HashSize
    );

    // Comparación en tiempo constante para evitar timing attacks
    return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
}
The CryptographicOperations.FixedTimeEquals() method is critical for preventing timing attacks. A standard equality comparison (==) would return as soon as it finds a mismatched byte, leaking information about how close the guess was.

Security Rationale

Why PBKDF2 Over BCrypt?

While BCrypt is a popular choice, this implementation uses PBKDF2 for several reasons:
PBKDF2 provides fine-grained control over iteration count, salt size, hash size, and underlying algorithm. This allows tuning for specific security requirements without library limitations.
PBKDF2 is explicitly recommended by NIST SP 800-132 for password-based key derivation. Organizations with compliance requirements often mandate NIST-approved algorithms.
PBKDF2 via Rfc2898DeriveBytes is built into .NET Core with optimized implementations. BCrypt requires third-party libraries which add dependencies and potential supply chain risks.
The hash format includes the iteration count, making it easy to increase security parameters over time as computing power increases. When a user logs in with an old hash, the system can detect it and automatically rehash with updated parameters.

Constant-Time Comparison

The verification process uses CryptographicOperations.FixedTimeEquals() to prevent timing attacks:
// ❌ VULNERABLE: Early return leaks information
if (actualHash[0] != expectedHash[0]) return false;
if (actualHash[1] != expectedHash[1]) return false;
// ...

// ✅ SECURE: Fixed-time comparison
return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
Timing attacks exploit the fact that comparison operations can take different amounts of time depending on where the first difference occurs. An attacker can use this timing information to gradually guess the correct hash byte-by-byte.

Integration Example

The password service is injected and used during registration:
Services/AuthService.cs
public async Task<AuthResponse> RegisterAsync(RegisterRequest request, string ipAddress)
{
    var user = new User
    {
        Username = request.Username,
        Email = request.Email.ToLower(),
        PasswordHash = _passwordService.Hash(request.Password)
    };

    _db.Users.Add(user);
    await _db.SaveChangesAsync();

    return await CreateAuthResponseAsync(user, ipAddress);
}
And during login verification:
Services/AuthService.cs
if (!_passwordService.Verify(request.Password, user.PasswordHash))
{
    user.FailedLoginAttempts++;

    if (user.FailedLoginAttempts >= MaxFailedAttempts)
    {
        user.LockoutEnd = DateTime.UtcNow.AddMinutes(LockoutMinutes);
    }

    await _db.SaveChangesAsync();
    throw new UnauthorizedAccessException("Credenciales inválidas.");
}

Standards Compliance

NIST SP 800-132

The implementation follows NIST Special Publication 800-132 recommendations:
  • Uses an approved pseudorandom function (HMAC-SHA512)
  • Minimum iteration count of 10,000 (we use 600,000)
  • Salt length of at least 128 bits (we use 256 bits)
  • Output length matches the underlying hash function

OWASP 2024 Guidelines

Complies with OWASP Password Storage Cheat Sheet:
  • 600,000 iterations for PBKDF2-HMAC-SHA512 (2024 recommendation)
  • Cryptographically random salt per password
  • Salt stored with the hash
  • Constant-time comparison for verification
OWASP iteration count recommendations increase over time as computing power improves. The 2024 guideline is 600,000 iterations for PBKDF2-HMAC-SHA512, up from 210,000 in 2023.

Performance Considerations

With 600,000 iterations, password hashing is intentionally slow:
  • Registration: ~200-300ms per password hash
  • Login: ~200-300ms per password verification
This computational cost is by design. It makes brute-force attacks prohibitively expensive while remaining acceptable for legitimate authentication. Never reduce the iteration count to improve performance.

Best Practices

  1. Never log passwords - The implementation never logs the plaintext password, only email and events
  2. Use HTTPS - Always transmit passwords over encrypted connections
  3. Validate input - Enforce password complexity requirements before hashing
  4. Rate limiting - See Account Lockout for failed attempt protection
  5. Regular updates - Monitor OWASP guidelines and update iteration counts as recommended

Build docs developers (and LLMs) love