In modern web and software development, securely handling user passwords is crucial to prevent security breaches. Storing plaintext passwords is a critical security risk, and hashing them using robust algorithms is the standard practice. In this article, we will explore the concept of password hashing, the importance of using salts, and how to implement secure password hashing in C# and PHP using Argon2 and bcrypt. We will also cover best practices, common mistakes, and additional security considerations when implementing user authentication in a web application.
Why Hash Passwords?
Storing passwords in plaintext is an enormous security risk. If a database is compromised, attackers will have immediate access to all user credentials. Instead, passwords should be hashed, making them virtually irreversible.
Key Goals of Secure Password Storage:
- One-way transformation – A password hash should not be reversible.
- Uniqueness – Identical passwords should not yield the same hash.
- Resistance to brute-force attacks – The hashing process should be computationally expensive to slow down attackers.
- Resistance to precomputed attacks (Rainbow Tables) – Hashing should incorporate unique salts to prevent dictionary-based attacks.
- Scalability and adaptability – Algorithms should allow fine-tuning to increase computational difficulty as hardware improves.
- Secure verification – User authentication should be implemented in a way that prevents timing attacks and unauthorized access.
What is Salting?
Salting is the process of adding a random, unique string (salt) to each password before hashing it. This ensures that even if two users choose the same password, their stored hashes will be different.
Without salting:
SHA-256("password123")
→ef92b778bafee9a1...
- Another user with “password123” gets the same hash.
With salting:
SHA-256("randomSalt1password123")
→a1b2c3d4e5...
SHA-256("randomSalt2password123")
→f7g8h9i0j1...
By adding a unique salt, attackers cannot use precomputed hash tables (rainbow tables) to reverse hashes.
Where to Store the Salt?
- Store the salt alongside the hash in the database (not secret).
- Do not reuse salts – each password should have a unique salt.
- Use sufficiently long salts (16 bytes or more recommended).
Implementing Secure Password Hashing
1. Using Argon2 for Password Hashing in C#
Argon2 is the recommended hashing algorithm because it is memory-hard, making it resistant to GPU-based attacks.
Installation
First, install the Konscious.Security.Cryptography library via NuGet:
dotnet add package Konscious.Security.Cryptography
C# Implementation of Argon2 Password Hashing
using System;
using System.Text;
using Konscious.Security.Cryptography;
using System.Security.Cryptography;
public class Argon2Hasher
{
public static string HashPassword(string password, out string salt)
{
byte[] saltBytes = new byte[16];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(saltBytes);
}
salt = Convert.ToBase64String(saltBytes);
using (var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)))
{
argon2.Salt = saltBytes;
argon2.DegreeOfParallelism = 4;
argon2.MemorySize = 65536;
argon2.Iterations = 4;
byte[] hashBytes = argon2.GetBytes(32);
return Convert.ToBase64String(hashBytes);
}
}
public static bool VerifyPassword(string password, string salt, string storedHash)
{
byte[] saltBytes = Convert.FromBase64String(salt);
using (var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)))
{
argon2.Salt = saltBytes;
argon2.DegreeOfParallelism = 4;
argon2.MemorySize = 65536;
argon2.Iterations = 4;
byte[] hashBytes = argon2.GetBytes(32);
return Convert.ToBase64String(hashBytes) == storedHash;
}
}
}
How to use it in the code
Here is an example of the usage of the class Argon2Hasher
to verificy a password:
using System;
class Program
{
static void Main()
{
string password = "SuperSicura123!";
string salt;
// Genera l'hash della password
string hashedPassword = Argon2Hasher.HashPassword(password, out salt);
Console.WriteLine("Password Hashata: " + hashedPassword);
Console.WriteLine("Salt: " + salt);
// Simuliamo il login: verifichiamo la password
bool isValid = Argon2Hasher.VerifyPassword("SuperSicura123!", salt, hashedPassword);
Console.WriteLine("Verifica Password Corretta: " + isValid);
// Proviamo con una password errata
bool isWrong = Argon2Hasher.VerifyPassword("PasswordErrata", salt, hashedPassword);
Console.WriteLine("Verifica Password Errata: " + isWrong);
}
}
How It Works
- Salt generation: A 16-byte random salt is created for each password.
- Hashing: Argon2id is configured with 4 threads, 64MB memory, and 4 iterations.
- Verification: The entered password is rehashed using the stored salt and compared to the saved hash.
Why Argon2id?
- ✅ Resistant to brute-force attacks
- ✅ Strong protection against GPU-based attacks
- ✅ Recommended by OWASP as the best option combining Argon2i and Argon2d
Database Schema
Here’s an example of how you can store hashed passwords and salts:
id | username | password_hash | salt |
---|---|---|---|
1 | mario | b64HashedPass | b64Salt |
During login:
- Retrieve the stored
salt
andhash
. - Generate a new hash from the entered password and the stored salt.
- If they match, authentication succeeds.
Conclusion
Argon2id is a modern, secure algorithm for password hashing.
- Each password uses a unique, random salt.
- Great defense against brute-force and GPU attacks.
- Easy to implement in any C# authentication system.
Feel free to use this code in your project or build it into a reusable authentication module.
Using Bcrypt for Password Hashing in C#
Bcrypt is another widely used password hashing algorithm that automatically handles salting.
Installation
dotnet add package BCrypt.Net-Next
C# Implementation of Bcrypt Password Hashing
using System;
using BCrypt.Net;
public class BcryptHasher
{
public static string HashPassword(string password, int workFactor = 12)
{
return BCrypt.Net.BCrypt.HashPassword(password, workFactor);
}
public static bool VerifyPassword(string password, string storedHash)
{
return BCrypt.Net.BCrypt.Verify(password, storedHash);
}
}
Verifying a Password in PHP using Bcrypt
$stmt = $pdo->prepare("SELECT password_hash FROM users WHERE username = ?");
$stmt->execute([$_POST['username']]);
$user = $stmt->fetch();
if ($user && password_verify($_POST['password'], $user['password_hash'])) {
echo "Login successful!";
} else {
echo "Invalid username or password.";
}
Conclusion
Implementing secure password hashing is essential for protecting user data. By using Argon2 or Bcrypt, developers can enhance security against brute-force attacks. In summary:
✅ Argon2id is the most secure algorithm, ideal for modern applications.
✅ Bcrypt is still a strong option, offering automatic salting.
✅ Salting ensures uniqueness, preventing precomputed attacks.
✅ Never store plaintext passwords—always hash them before saving.
✅ Verify passwords securely by retrieving stored credentials and using proper comparison techniques.
Whether using C# or PHP, implementing secure password hashing correctly is crucial in any authentication system.
Leave a Reply