Formal evaluation and analysis of Password Hashing #402

Closed
opened 2026-04-17 09:27:41 +00:00 by a24vinla · 9 comments
Collaborator

We need to evaluate what password encryption service to use.

Related Issues: #148

We need to evaluate what password encryption service to use. Related Issues: #148
Collaborator

Argon2 (Argon2id)

Advantages

Designed to resist GPU/ASIC attacks through memory hardness,

  • Highly configurable, means that you can configure memory, time (iterations), parallelism,

  • Considered modern best practice,

  • Won the PHC (Password hashing competition) in 2015.
    Disadvantages

  • Quite Complex to configure therefore requires careful parameter tuning,

  • not available in very old libraries or legacy systems.

bcrypt

Advantages

Widely supported and tested,

  • A single work factor (cost) makes configuration simple,

  • Resistant to brute force through computational cost.
    Disadvantages

  • Not memory-hard so more vulnerable to GPU/ASIC attacks,

  • Has a 72-byte limit on password length.

PBKDF2

Advantages

  • Standardized,

  • Available in most languages and frameworks,

  • Easy to implement.
    Disadvantages

  • Purely CPU-bound and not memory-hard making it weaker against GPU/ASIC attacks,

  • Requires high iteration counts for strong security.

Preferred option:

Argon2id is probably to recommend as the most suitable algorithm specifically (Argon2id), as it is designed to be resistant to both GPU and ASIC attacks, making large-scale brute-force attacks significantly more expensive for an attacker than bcrypt or PBKDF2.

bcrypt can be an acceptable alternative incase Argon2id is not available as Argon2id is newer and don't support very old libraries and legacy systems. And PBKDF2 should only be used when required.

Performance

-Slow hashing algorithms add intentional computational costs to protect from brute-force attacks, however this cost must be balanced to avoid degrading the performance off authentication for the users.

Argon2id is suitable here as well as it allows the tuning of its cost parameters specifically memory cost, time (iterations) cost and parallelism.

To balance the security and performance we can set some "hard" rules such as:

  • The hashing process should take "x" time which should be noticeable but acceptable amount of time, so that it does not impact the user experience while it still slows down brute-force attempts.
  • The parameters should be set in a way, so that the authentication process remains responsive at all times.
  • The parameters can and should be reviewed periodically and then adjusted based on performance changes over time.
  • Links used during research:
    Recommending the last link if you want to read and understand the differences quickly.

What is bcrypt

What is Argon2
Password Hashing: Scrypt, Bcrypt and ARGON2

Complete Guide to PBKDF2 vs bcrypt vs Argon2 for Password Hashing

### Argon2 (Argon2id) Advantages Designed to resist GPU/ASIC attacks through memory hardness, - Highly configurable, means that you can configure memory, time (iterations), parallelism, - Considered modern best practice, - Won the PHC (Password hashing competition) in 2015. Disadvantages - Quite Complex to configure therefore requires careful parameter tuning, - not available in very old libraries or legacy systems. ### bcrypt Advantages Widely supported and tested, - A single work factor (cost) makes configuration simple, - Resistant to brute force through computational cost. Disadvantages - Not memory-hard so more vulnerable to GPU/ASIC attacks, - Has a 72-byte limit on password length. ### PBKDF2 Advantages - Standardized, - Available in most languages and frameworks, - Easy to implement. Disadvantages - Purely CPU-bound and not memory-hard making it weaker against GPU/ASIC attacks, - Requires high iteration counts for strong security. ### Preferred option: Argon2id is probably to recommend as the most suitable algorithm specifically (Argon2id), as it is designed to be resistant to both GPU and ASIC attacks, making large-scale brute-force attacks significantly more expensive for an attacker than bcrypt or PBKDF2. bcrypt can be an acceptable alternative incase Argon2id is not available as Argon2id is newer and don't support very old libraries and legacy systems. And PBKDF2 should only be used when required. ### Performance -Slow hashing algorithms add intentional computational costs to protect from brute-force attacks, however this cost must be balanced to avoid degrading the performance off authentication for the users. Argon2id is suitable here as well as it allows the tuning of its cost parameters specifically memory cost, time (iterations) cost and parallelism. ### To balance the security and performance we can set some "hard" rules such as: - The hashing process should take "x" time which should be noticeable but acceptable amount of time, so that it does not impact the user experience while it still slows down brute-force attempts. - The parameters should be set in a way, so that the authentication process remains responsive at all times. - The parameters can and should be reviewed periodically and then adjusted based on performance changes over time. - Links used during research: Recommending the last link if you want to read and understand the differences quickly. [What is bcrypt](https://jumpcloud.com/it-index/what-is-bcrypt) [What is Argon2](https://jumpcloud.com/it-index/what-is-argon2) [Password Hashing: Scrypt, Bcrypt and ARGON2](https://medium.com/@mpreziuso/password-hashing-pbkdf2-scrypt-bcrypt-and-argon2-e25aaf41598e) [Complete Guide to PBKDF2 vs bcrypt vs Argon2 for Password Hashing](https://www.locksy.dev/blog/complete-guide-to-pbkdf2-vs-bcrypt-vs-argon2-for-password-hashing)
Collaborator

How they work

  • Argon2id: Combines the memory-hard computations and multiple passes to resist GPU/ASIC attacks, combined with a data-dependent and data-independent memory access.
  • bcrypt: uses the blowfish cipher internally and repeatedly applies it to the password + salt
  • PBKDF2: applies a standard hash function such as (SHA-256) to the password + salt and hashes the password + salt multiple times, potentially even thousands to hundreds of thousands times

Other sensitive data such as emails.

  • Encryption at rest, encrypt sensitive data using a strong algorithm such as AES-256.
  • If we encrypt data that is frequently accesses such as emails, then we can store a hashed version for lookups while keeping the full email encrypted.
  • Access control, we can implement role-based rights to restrict who can access sensitive fields
  • Transport security, we always use secure protocols when sending or receiving sensitive data (e.g., HTTPS)
### How they work - Argon2id: Combines the memory-hard computations and multiple passes to resist GPU/ASIC attacks, combined with a data-dependent and data-independent memory access. - bcrypt: uses the blowfish cipher internally and repeatedly applies it to the password + salt - PBKDF2: applies a standard hash function such as (SHA-256) to the password + salt and hashes the password + salt multiple times, potentially even thousands to hundreds of thousands times ### Other sensitive data such as emails. - Encryption at rest, encrypt sensitive data using a strong algorithm such as AES-256. - If we encrypt data that is frequently accesses such as emails, then we can store a hashed version for lookups while keeping the full email encrypted. - Access control, we can implement role-based rights to restrict who can access sensitive fields - Transport security, we always use secure protocols when sending or receiving sensitive data (e.g., HTTPS)
Collaborator

Practical implementation of Argon2id in C#

The package can be install either as NuGet or:

Install-Package Konscious.Security.Cryptography.Argon2 -Version 1.3.0

Or

dotnet add package Konscious.Security.Cryptography.Argon2

Examples:

Complete implementation example

using System;
using System.Security.Cryptography;
using System.Text;
using Konscious.Security.Cryptography;

public class PasswordHasher
{
    private const int SaltSize = 16; // 128 bits
    private const int HashSize = 32; // 256 bits
    private const int DegreeOfParallelism = 8; // Number of threads to use
    private const int Iterations = 4; // Number of iterations
    private const int MemorySize = 1024 * 1024; // 1 GB

    public string HashPassword(string password)
    {
        // Generate a random salt
        byte[] salt = new byte[SaltSize];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
        }

        // Create hash
        byte[] hash = HashPassword(password, salt);

        // Combine salt and hash
        var combinedBytes = new byte[salt.Length + hash.Length];
        Array.Copy(salt, 0, combinedBytes, 0, salt.Length);
        Array.Copy(hash, 0, combinedBytes, salt.Length, hash.Length);

        // Convert to base64 for storage
        return Convert.ToBase64String(combinedBytes);
    }

    private byte[] HashPassword(string password, byte[] salt)
    {
        var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password))
        {
            Salt = salt,
            DegreeOfParallelism = DegreeOfParallelism,
            Iterations = Iterations,
            MemorySize = MemorySize
        };

        return argon2.GetBytes(HashSize);
    }

    public bool VerifyPassword(string password, string hashedPassword)
    {
        // Decode the stored hash
        byte[] combinedBytes = Convert.FromBase64String(hashedPassword);

        // Extract salt and hash
        byte[] salt = new byte[SaltSize];
        byte[] hash = new byte[HashSize];
        Array.Copy(combinedBytes, 0, salt, 0, SaltSize);
        Array.Copy(combinedBytes, SaltSize, hash, 0, HashSize);

        // Compute hash for the input password
        byte[] newHash = HashPassword(password, salt);

        // Compare the hashes
        return CryptographicOperations.FixedTimeEquals(hash, newHash);
    }
}

Usage example

public class Program
{
    public static void Main()
    {
        var hasher = new PasswordHasher();

        // Hash a password
        string password = "MySecurePassword123!";
        string hashedPassword = hasher.HashPassword(password);
        Console.WriteLine($"Hashed Password: {hashedPassword}");

        // Verify the password
        bool isValid = hasher.VerifyPassword(password, hashedPassword);
        Console.WriteLine($"Password is valid: {isValid}");
    }
}

Implement Argon2id in C#

### Practical implementation of Argon2id in C# The package can be install either as NuGet or: `Install-Package Konscious.Security.Cryptography.Argon2 -Version 1.3.0` Or `dotnet add package Konscious.Security.Cryptography.Argon2` ### Examples: **Complete implementation example** ``` using System; using System.Security.Cryptography; using System.Text; using Konscious.Security.Cryptography; public class PasswordHasher { private const int SaltSize = 16; // 128 bits private const int HashSize = 32; // 256 bits private const int DegreeOfParallelism = 8; // Number of threads to use private const int Iterations = 4; // Number of iterations private const int MemorySize = 1024 * 1024; // 1 GB public string HashPassword(string password) { // Generate a random salt byte[] salt = new byte[SaltSize]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(salt); } // Create hash byte[] hash = HashPassword(password, salt); // Combine salt and hash var combinedBytes = new byte[salt.Length + hash.Length]; Array.Copy(salt, 0, combinedBytes, 0, salt.Length); Array.Copy(hash, 0, combinedBytes, salt.Length, hash.Length); // Convert to base64 for storage return Convert.ToBase64String(combinedBytes); } private byte[] HashPassword(string password, byte[] salt) { var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)) { Salt = salt, DegreeOfParallelism = DegreeOfParallelism, Iterations = Iterations, MemorySize = MemorySize }; return argon2.GetBytes(HashSize); } public bool VerifyPassword(string password, string hashedPassword) { // Decode the stored hash byte[] combinedBytes = Convert.FromBase64String(hashedPassword); // Extract salt and hash byte[] salt = new byte[SaltSize]; byte[] hash = new byte[HashSize]; Array.Copy(combinedBytes, 0, salt, 0, SaltSize); Array.Copy(combinedBytes, SaltSize, hash, 0, HashSize); // Compute hash for the input password byte[] newHash = HashPassword(password, salt); // Compare the hashes return CryptographicOperations.FixedTimeEquals(hash, newHash); } } ``` **Usage example** ``` public class Program { public static void Main() { var hasher = new PasswordHasher(); // Hash a password string password = "MySecurePassword123!"; string hashedPassword = hasher.HashPassword(password); Console.WriteLine($"Hashed Password: {hashedPassword}"); // Verify the password bool isValid = hasher.VerifyPassword(password, hashedPassword); Console.WriteLine($"Password is valid: {isValid}"); } } ``` ### Link: [Implement Argon2id in C#](https://www.thatsoftwaredude.com/content/14030/implementing-argon2id-password-hashing-in-c)
Collaborator

Microsoft Official dot net package for C#

Microsoft does have an official approach to password hashing thats part of ASP.NET core identity. Unlike with Argon2id microsoft does not use Argon2 or bcrypt instead it uses PBKDF2 with SHA256.

The functionality is implemented through the PasswordHasher class which is automatically used when implementing the autoentication with ASP.NET. With it developers do not need to manually handle the salts, iterations counts or the hashing format as these are instead managed internally by the framework. This will reduced the risk of developer misconfiguration.

PBKDF2 is standardzied and widely supported which means that it works across many platforms and environments and its well tested and considered secure with a high iteration count.

From an implementation perspective, by using Microsofts built in password hasher we avoid the set-ups required when using a different hashing algorithm as it comes with .NET. This also reduces the risk of developers installing the wrong dependencies as it comes with our already downloaded VSC

Rfc2898DeriveBytes.Pbkdf2 Method Definition

PasswordHasher Class Definition

Videos

Password Hashing in C# (.NET 8, PBKDF2 + SQL Server)

### Microsoft Official dot net package for C# Microsoft does have an official approach to password hashing thats part of ASP.NET core identity. Unlike with Argon2id microsoft does not use Argon2 or bcrypt instead it uses PBKDF2 with SHA256. The functionality is implemented through the PasswordHasher <TUser> class which is automatically used when implementing the autoentication with ASP.NET. With it developers do not need to manually handle the salts, iterations counts or the hashing format as these are instead managed internally by the framework. This will reduced the risk of developer misconfiguration. PBKDF2 is standardzied and widely supported which means that it works across many platforms and environments and its well tested and considered secure with a **high iteration count.** From an implementation perspective, by using Microsofts built in password hasher we avoid the set-ups required when using a different hashing algorithm as it comes with .NET. This also reduces the risk of developers installing the wrong dependencies as it comes with our already downloaded VSC ### Links: [Rfc2898DeriveBytes.Pbkdf2 Method Definition](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.pbkdf2?view=net-10.0) [PasswordHasher<TUser> Class Definition](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.passwordhasher-1?view=aspnetcore-10.0) ### Videos [Password Hashing in C# (.NET 8, PBKDF2 + SQL Server)](https://www.youtube.com/watch?v=ydUxs-1HHB8)
Collaborator

Henrik kinda answered this one with: "If you have dependencies, make an evaluation and decide from that". Us group leaders will look through this evaluation and decide which one to use.

Henrik kinda answered this one with: "If you have dependencies, make an evaluation and decide from that". Us group leaders will look through this evaluation and decide which one to use.
Collaborator

Me and @a24vinla has decided that we will use PBKDF2 as it is built into Dotnet and its easier to configure. It is also older (been around since 2000), making it unlikely to be abandoned by Microsoft as they still use it.

We should try to implement it and if the result is bad or it is harder to configure than we anticipated we could consider a change to Argon2id.

Me and @a24vinla has decided that we will use PBKDF2 as it is built into Dotnet and its easier to configure. It is also older (been around since 2000), making it unlikely to be abandoned by Microsoft as they still use it. We should try to implement it and if the result is bad or it is harder to configure than we anticipated we could consider a change to Argon2id.
Collaborator

Barbecue decision has been made the issue can be closed.

Barbecue decision has been made the issue can be closed.
Collaborator

We need to add this to the wiki , there exist an wiki page for it. We can basically just copy the evaluation posted here and add the conclusion to use PBKDF2 at the bottom.

We need to add this to the [wiki](https://git.webug.se/Andras/BoundlessFlowCampus2K/wiki/Evaluation-Password-hashing) , there exist an wiki page for it. We can basically just copy the evaluation posted here and add the conclusion to use PBKDF2 at the bottom.
c24danli reopened this issue 2026-05-08 12:53:50 +00:00
Collaborator

Created separate issue Write wiki about our choice of password encryption #855

Created separate issue Write wiki about our choice of password encryption #855
Sign in to join this conversation.
No milestone
No project
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Andras/BoundlessFlowCampus2K#402
No description provided.