`Hash` Password in C? Bcrypt/PBKDF2
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
For password storage in C#, the real goal is not “hash a string” but “store passwords with a slow, salted password hashing algorithm and verify them safely later”. Between bcrypt and PBKDF2, both can be acceptable, but PBKDF2 has strong built-in support in .NET while bcrypt typically comes from a third-party library.
Choosing Between bcrypt and PBKDF2
The first practical rule is that passwords should not be stored with a fast hash such as SHA-256 alone. Password hashing needs a per-password salt and a cost setting that makes offline guessing expensive.
In current guidance, Argon2id is generally preferred when available, bcrypt remains acceptable in many legacy systems, and PBKDF2 is especially common in .NET because it is built in and widely approved in regulated environments.
For a C# application, PBKDF2 is often the easiest defensible choice because you can implement it with standard library APIs and avoid inventing your own format.
A Runnable PBKDF2 Example in C#
This example uses a random salt, Rfc2898DeriveBytes.Pbkdf2, and constant-time verification.
That format stores the algorithm label, iteration count, salt, and derived key. Storing the parameters with the hash makes future upgrades easier.
Where bcrypt Fits
bcrypt is still widely used and can be a reasonable choice if your team already depends on a mature bcrypt library such as BCrypt.Net-Next. Its main strengths are simplicity and an adaptive work factor.
However, bcrypt has an important input limit around 72 bytes in many implementations. That matters if you allow long passphrases or arbitrary Unicode-heavy input. It is one reason teams often prefer Argon2id or PBKDF2 in newer designs.
A typical bcrypt usage in C# looks like this:
That is concise, but it depends on an external package and the library’s behavior should still be reviewed before adoption.
Operational Guidance
Whatever algorithm you choose, store enough metadata to support migration later. A good stored format includes:
- algorithm name
- work factor or iteration count
- salt
- derived hash
Then, when your policy changes, you can verify old hashes and transparently rehash on the next successful login.
Also remember that password hashing is only one part of the authentication story. Rate limiting, MFA, breached-password screening, and secure reset flows still matter.
What Not to Do
Do not write your own crypto primitives. Do not store raw SHA-256 of the password. Do not reuse the same salt across users. Do not compare hashes with ordinary string equality when a constant-time comparison API is available.
Also avoid outdated .NET examples that use old instance patterns when newer one-shot APIs are available. In current .NET guidance, the one-shot Pbkdf2 method is the cleaner approach.
Common Pitfalls
The biggest pitfall is choosing a fast general-purpose hash instead of a password hashing scheme. That makes brute-force attacks dramatically easier.
Another mistake is omitting the salt or hard-coding it globally. Each password needs its own random salt.
A third issue is forgetting the algorithm’s limits. bcrypt’s password-length behavior is not the same as PBKDF2’s, so the choice affects input handling and migration planning.
Summary
- In C#, PBKDF2 is often the easiest secure built-in option for password storage.
- bcrypt is still acceptable in many systems but commonly relies on a third-party package and has input-length limits.
- Always use a unique random salt and store algorithm parameters with the hash.
- Verify using constant-time comparison.
- Do not use plain SHA-256 or any other fast hash by itself for password storage.
Related reading
- HashiCorp Vault 403 Permission Denied issue with Kubernetes Auth
- Help me understand pack, openssl_random_pseudo_bytes and mt_rand for salting passwords
- Hide password with ••••••• in a textField
- How AWS Cognito User Pool defends against bruteforce attacks
- HashSetT versus DictionaryK, V w.r.t searching time to find if an item exists
- Haskell equivalent of C 5 async/await
- How can I add NSAppTransportSecurity to my info.plist file?
- How can I allow a Group to assume a Role?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.