Implementing a Password Hasher in Your App (Step‑by‑Step)

Password Hasher: Best Practices for Secure Password StorageStoring user passwords safely is one of the most important responsibilities for any developer or system administrator. A compromised password database can destroy user trust, lead to account takeover, and expose sensitive personal or financial data. This article explains why password hashing matters, explores current recommended hashing algorithms and configurations, details implementation best practices, and covers related operational and threat considerations.


Why hashing (and not encryption) is the right approach

When users create passwords, you should never store them in plaintext. Encryption is reversible — anyone who obtains the encryption key can decrypt all passwords — whereas hashing is a one-way function designed to make it infeasible to recover the original password from the stored hash. For authentication, you compare the hash of the user-supplied password with the stored hash; if they match, the password is correct.

Hashing is the correct approach because it minimizes the value of a stolen password store: an attacker receives computationally expensive hashes instead of usable passwords.


Key properties of a secure password hashing function

  • Slow and CPU- or memory-intensive: To resist brute-force and dictionary attacks, hashing functions should be deliberately slow and resource-expensive.
  • Use of a unique salt per password: Salts prevent rainbow-table attacks and make identical passwords produce different hashes.
  • Tunable parameters: You should be able to increase computational cost over time as hardware improves.
  • Resistant to GPU/ASIC parallelization: Memory-hard algorithms make parallel attacks harder and more expensive.
  • Well-vetted and standardized: Prefer algorithms that have undergone public review and adoption.

  • Argon2idbest overall choice for new applications. It provides memory-hard resistance to GPU/ASIC attacks, allows configurable time, memory, and parallelism parameters, and is the winner of the 2015 Password Hashing Competition. Use Argon2id to get protection against both side-channel and GPU-accelerated attacks.
  • bcrypt — Good legacy option with wide support and a long track record. It’s slower to configure (work factor) but not memory-hard, so GPUs and ASICs can attack bcrypt faster than Argon2id for the same cost.
  • scrypt — Memory-hard and a solid choice where Argon2 isn’t available, but less commonly used in modern libraries.
  • Avoid general-purpose hashes (SHA-256, SHA-512, MD5) for password hashing unless you wrap them in a proper slow KDF with salts and many iterations (e.g., PBKDF2). Even then prefer Argon2id or bcrypt.

Recommended short answer: Argon2id (or bcrypt where Argon2 is unavailable).


Salting: why and how

  • Use a unique, cryptographically-random salt per password (at least 16 bytes).
  • Store the salt alongside the hash in your user database (it’s not secret).
  • Salts ensure identical passwords hash differently, preventing precomputed lookup tables.

Example storage format (common practice): algorithm, parameters, salt, hash — e.g., Argon2 encoded string.


Parameter selection and tuning

Hashing should be tunable so you can increase cost as hardware improves. For Argon2id, parameters include memory (KB/MB), time (iterations), and parallelism (threads). For bcrypt, the work factor (cost/exponent) controls iterations.

Tuning guidelines:

  • Measure authentication latency on your production-like hardware; aim for an authentication time of about 100–500 ms per hash verification for interactive logins. Higher can frustrate users; lower reduces security margin.
  • For Argon2id in 2025, a starting example might be: memory = 64–256 MB, iterations = 2–4, parallelism = number of cores (1–4). Adjust upward over time.
  • For bcrypt, a cost between 12 and 14 is commonly recommended in modern environments (measure to confirm).
  • Monitor CPU/memory load and adapt parameters to keep site responsiveness acceptable.

Peppering: optional additional secret

A pepper is a server-side secret value added to the password before hashing. Unlike a salt, it’s not stored with the hash and thus provides an extra layer if the database is leaked but the pepper remains secret.

  • Store pepper in a secure location such as an HSM or secrets manager, not in the database.
  • Use pepper carefully: it complicates password rotation and recovery, and if lost, may invalidate all stored passwords.
  • Pepper is optional; properly salted and parameterized hashing (Argon2id) is usually sufficient.

Secure storage format and versioning

  • Store algorithm, parameters, salt, and hash together so you can change algorithms later. Example encoded form: \(argon2id\)v=19\(m=65536,t=3,p=4\)$.
  • Include a version tag or algorithm identifier to support rehashing users’ passwords with stronger parameters when you change policy.
  • On login, check stored metadata and rehash the password with new parameters if stored parameters are outdated.

Implementation checklist

  • Use well-tested, maintained libraries — don’t implement your own hashing algorithm.
  • Generate salts with a CSPRNG (cryptographically secure random number generator), e.g., getrandom, /dev/urandom, or language-specific secure RNG.
  • Use constant-time comparison when comparing hashes to avoid timing attacks.
  • Enforce strong password policies (length, entropy) and rate-limit login attempts; hashing alone is not enough.
  • Protect the database and backups with proper access controls and encryption at rest.
  • Use secure channels (TLS) for password transmission; consider multi-factor authentication to reduce dependence on passwords.

Migration strategies

  • For legacy plaintext or weakly-hashed passwords, require users to reset passwords or perform a “migrate on next login” rehash: when a user authenticates with a legacy hash, verify and then store a new Argon2id/bcrypt hash.
  • Use feature flags to roll out stronger hashing gradually.
  • Keep compatibility metadata so older entries can be upgraded transparently.

Operational considerations and threat model

  • Assume attackers may access your user database. Protect against offline brute-force by using memory-hard, slow hashing.
  • Defend against online attacks with rate limiting, lockouts, CAPTCHA, IP throttling, and MFA.
  • Rotate peppers and keys stored in secrets managers safely; plan for key rotation without breaking authentication (e.g., support multiple peppers during transition).
  • Monitor for suspicious login patterns and credential-stuffing attempts.

Testing and monitoring

  • Continuously benchmark hashing performance as part of CI/CD when you update libraries or server hardware.
  • Log authentication latency and failure rates (avoid logging plaintext passwords).
  • Use fuzzing and security reviews on authentication code paths.

Example code snippets (conceptual)

  • Use language-native, well-maintained libraries: libsodium/argon2 bindings, bcrypt libraries, or built-in frameworks that wrap secure KDFs.
  • Example Argon2 pseudocode (use library functions in real code):
password = get_password_input() salt = secure_random_bytes(16) hash = argon2id_hash(password, salt, memory=65536, iterations=3, parallelism=2) store(user_id, format_metadata(hash, salt, params)) 

Common mistakes to avoid

  • Storing plaintext passwords.
  • Using unsalted or fixed-salt hashes.
  • Using fast hashes (SHA-family) without a proper slow KDF.
  • Choosing parameters that are too low to be effective.
  • Rolling your own crypto primitives.
  • Hardcoding secrets (pepper) in source code or config files checked into version control.

Regulatory and compliance notes

Many regulations and standards (e.g., PCI-DSS for payment data) require strong password handling and storage. Follow applicable industry guidance and document your security controls, hashing algorithms, and rotation policies for audits.


Conclusion

Secure password storage is a combination of choosing the right algorithm (Argon2id preferred), configuring parameters to be intentionally slow and memory-hard, using unique salts, protecting secrets (pepper, keys), and implementing operational controls (rate-limiting, MFA, monitoring). Design for upgradeability so you can rehash stored passwords with stronger settings over time.

If you want, I can: provide example code for a specific language (Python, Node.js, Java, Go), suggest exact Argon2id parameters tailored to your server hardware, or draft a migration plan from an existing weak-hash store.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *