Skip to main content
CryptographyMay 10, 2026• 8 min read

Building a Complete Password Encryption System

From user password to encrypted data storage. A complete solution for secure applications.

Build secure password systems with our Password Tools. Learn best practices for hashing and encryption.

How to encrypt a password: the short answer

Encrypting a password is usually the wrong move. A password you only need to check at login must be hashed with bcrypt or Argon2id, never encrypted: reversible storage hands an attacker every plaintext password as soon as the key leaks. Password based encryption is for the other case, where the password is what opens data you have to read again.

So the right answer depends on what you are actually trying to do:

  • Storing a login password: hash it with Argon2id (19 MiB of memory, 2 iterations) or bcrypt (cost factor 10 or higher). The original password is not recoverable, and that is the point.
  • Protecting data with a password: derive a key from the password (PBKDF2-HMAC-SHA256 or Argon2id) and encrypt with AES-256-GCM. The user keeps one secret and can still read the data later.
  • Sending a secret to another person: encrypt the file or message with a password the two of you agreed on out of band, then send the ciphertext over any channel you like.

Note: If a service can show you your original password, it is not hashing it. Treat that as a warning sign, not a feature.

Encrypting data with a password, step by step

Password based encryption needs different ingredients from hashing: a derived key, a nonce, and an authenticated cipher. This order is the one that holds up:

  1. Generate the salt: 16 random bytes from crypto.getRandomValues. The salt is public and is stored next to the ciphertext.
  2. Derive the key: PBKDF2-HMAC-SHA256 with the salt and an iteration count that costs about 100 to 300 ms on your hardware. OWASP puts the floor for server-side PBKDF2 at 600,000 iterations.
  3. Generate the nonce: 12 random bytes, fresh for every operation. With AES-GCM and a 96-bit nonce it is fed straight into the counter block.
  4. Encrypt: crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data). The authentication tag of the ciphertext is produced along with it.
  5. Package the result: salt, IV, ciphertext and the 16-byte tag in one record, for example [salt 16 | IV 12 | ciphertext | tag 16], and store it base64- or hex-encoded.

Warning: Do not reuse an IV with the same key, and do not store the key next to the ciphertext. Either mistake breaks the security of an otherwise correct cipher.

Parameters, and why decryption fails

The iteration count is a trade-off. OWASP names 600,000 iterations for server-side PBKDF2-HMAC-SHA256. In a browser that would mean seconds of waiting on every password entry, so the tool on this site uses a count that costs a fraction of a second on ordinary hardware. What matters is that you record the count you used.

Decryption needs exactly the same parameters. If the iteration count, salt, IV or authentication tag differ by one byte, the GCM check fails and the function reports an error without saying which value was wrong.

  • Auth tag: 16 bytes, verified during decryption and never compared with the password
  • Output format: hex only for an even number of digits, otherwise base64, so no byte is decoded from the wrong alphabet

Password based encryption: what it is and when to use it

Password based encryption (PBE) turns a human passphrase into a key and uses that key to encrypt the data, so the password alone gets the plaintext back. It is the opposite of hashing: a hash is one-way and only proves that a value matches, while encryption is reversible by design.

That gives you a simple rule. If you must read the data again — a backup, an export, a file you hand to someone — use password based encryption with a real key derivation function (PBKDF2, Argon2 or scrypt) and an authenticated cipher such as AES-256-GCM. If you only need to check a login, use a password hash such as bcrypt, and never encryption.

The Database Breach

It is Monday morning. You are sipping coffee when the security alert hits your inbox. Your user database has been leaked. Millions of passwords exposed.

But here is the twist: the attacker only got hashes, not plaintext passwords. And you used bcrypt with proper salt. Those passwords are still safe.

This is the difference between good and bad password storage. Let us explore how to do it right.

Hashing vs Encryption: Critical Difference

Passwords should never be encrypted. They should be hashed. Here is why:

Encryption is reversible. If someone gets the key, they can decrypt all passwords. Hashing is one-way. You cannot unhash a password.

When a user logs in, you hash their input and compare it to the stored hash. If they match, the password is correct. You never need to know the actual password.

Choosing the Right Algorithm

Not all hash algorithms are suitable for passwords. Here is what to use and what to avoid:

  • Use bcrypt - Industry standard, adaptive cost factor, built-in salt
  • Use Argon2 - Winner of Password Hashing Competition, memory-hard
  • Use scrypt - Memory-hard, good for cryptocurrencies
  • Never use MD5 - Broken, too fast, vulnerable to rainbow tables
  • Never use SHA-256 - Designed for speed, bad for passwords
  • Never use plain text - Unacceptable in any circumstance

Understanding bcrypt

Bcrypt is the most widely recommended password hashing algorithm. It has several key features that make it ideal for passwords.

First, it is slow. Bcrypt includes a cost factor (work factor) that makes hashing intentionally slow. This prevents brute force attacks. A cost factor of 10-12 is recommended for most applications.

Second, it automatically handles salt. Salt is random data added to each password before hashing. This prevents rainbow table attacks. Bcrypt generates and stores the salt automatically.

Implementation Guide

Here is how to implement password hashing in different languages:

Node.js (bcrypt)

const bcrypt = require('bcrypt'); const saltRounds = 12; // Hash password const hash = await bcrypt.hash(password, saltRounds); // Verify password const match = await bcrypt.compare(password, hash);

Python (bcrypt)

import bcrypt # Hash password password = b'super secret password' salt = bcrypt.gensalt(rounds=12) hashed = bcrypt.hashpw(password, salt) # Verify password if bcrypt.checkpw(password, hashed): print('Match')

Common Password Storage Mistakes

Even experienced developers make these mistakes:

  • Using fast hash algorithms - SHA-256, MD5 are designed for speed, making brute force easy
  • Not using salt - Without salt, identical passwords have identical hashes
  • Hardcoding salt - Salt must be unique per password, randomly generated
  • Insufficient cost factor - Too low and hashing is too fast; too high and it affects performance
  • Storing passwords in logs - Never log passwords, even for debugging

FAQ

Q.What is pepper?

A.Pepper is a secret key added to all passwords before hashing, unlike salt which is unique per password. It provides extra security if the database is leaked but the pepper key remains safe.

Q.How do I migrate to a better algorithm?

A.Rehash passwords on next login. When a user logs in with the old hash, verify it, then rehash with the new algorithm and store the new hash.

Q.Does password length matter with hashing?

A.Yes. Bcrypt truncates passwords at 72 bytes. Encourage users to use long passwords, but consider pre-hashing very long passwords if necessary.

Q.How to encrypt password-protected files in the browser?

A.Derive a 256-bit key from the passphrase with PBKDF2, encrypt with AES-256-GCM, and store the salt and IV next to the ciphertext — nothing to install and nothing uploaded. The AES-256-GCM encryptor does exactly these steps with the Web Crypto API, and the same recipe works for a message you paste into a chat or an issue tracker.

Q.Are encrypted passwords a hash or ciphertext?

A.Both show up in databases and they mean opposite things. A string like `$2b$12$...` is a bcrypt hash: 60 characters, impossible to reverse, and only useful for checking a login. Ciphertext with a salt and IV is encryption: reversible with the password, which is what you need when the data has to be read again. If a system can show you your original password, it is not hashing it.

Q.Is password based encryption the same as password hashing?

A.No. Password based encryption is reversible: the password derives a key and you can decrypt. Password hashing is one-way: you can only verify. Use encryption for data you must recover (backups, exports, messages) and hashing for credentials you must only compare. Storing login passwords with reversible encryption is the mistake that turns one database leak into every user's plaintext password.

References

  • OWASP Password Storage Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
  • NIST SP 800-63B – Digital Identity Guidelines: Authentication: https://pages.nist.gov/800-63-3/sp800-63b.html

Encrypt your data locally

Encrypt text or files with AES-256-GCM in your browser — no upload, no account.

Conclusion

Password-based encryption is only as strong as the key derivation around it. Build your workflow with the AES-256-GCM Encryptor.

Encryption background on zeyrovault.com

password encryptionPBKDF2key derivationAES-GCMsaltencrypted storage