PBKDF2 Hash Generator & Verifier
Derive PBKDF2 keys with an explicit salt encoding (hex, base64 or UTF-8 text) and verify passwords against real stored hashes — Django, passlib/PHC, Werkzeug, hash:salt or a bare digest. Output as hex, base64, a Django string or a PHC string, with a live benchmark and the OWASP iteration floors enforced in the UI. Everything runs in your browser.
Hash Generation Settings
Two hex digits per byte — the usual way to print a random salt.
600,000 — at or above the OWASP floor for SHA-256
Configuration: PBKDF2-HMAC-SHA-256, 600,000 iterations, 0-byte salt
Output: 32 bytes (256 bits)
OWASP floor for SHA-256: 600,000 iterations
Derived Key
The derived key will appear here
Enter a password and a salt, then click "Generate Hash"
Argon2id is the better default unless you need FIPS
PBKDF2 is computation-hard but not memory-hard: every iteration is a single HMAC, so a GPU can run thousands of candidate passwords in parallel per card. Argon2id forces an attacker to buy memory as well as compute, which is why OWASP lists it first. Reach for PBKDF2 when FIPS 140-2/140-3 or NIST SP 800-132 compliance ties your hands — it is the only one of the four modern password hashes on the NIST-approved list.
OWASP iteration floors (Password Storage Cheat Sheet)
| PRF | Minimum iterations | Notes |
|---|---|---|
| PBKDF2-HMAC-SHA256 | 600,000 | The default for new work. |
| PBKDF2-HMAC-SHA512 | 210,000 | Fewer iterations for the same work — each one costs more on 64-bit CPUs. |
| PBKDF2-HMAC-SHA1 | 1,300,000 | Legacy only — migrate on next successful login. |
| PBKDF2-HMAC-SHA384 | 210,000* | *Not published by OWASP. SHA-384 is truncated SHA-512 and costs the same per iteration, so this tool reuses the SHA-512 figure. |
These are floors, not targets. The real target is wall-clock time: tune iterations until a single derivation takes 250–400 ms on your production hardware, then re-benchmark every 12–24 months.
Salt encoding: the reason hashes fail to reproduce
PBKDF2 is defined over bytes. The string 0102030405060708 is 8 bytes when read as hex and 16 bytes when read as text — and the two produce completely different derived keys. That is why this tool asks you which encoding you mean instead of guessing.
- • Hex — what most language snippets print when they show a random salt. 16 random bytes become 32 hex characters.
- • Base64 — what passlib and PHC strings carry (passlib uses adapted base64: "." instead of "+", no padding).
- • Text (UTF-8) — what Django and Werkzeug store. Their salts are literal alphanumeric characters, hashed as their UTF-8 bytes.
If a hash from another system will not verify here, check this first: it is far more often a salt-encoding mismatch than a wrong password.
Technical specifications
- • Algorithm: PBKDF2 as specified in RFC 8018 (PKCS #5 v2.1, which obsoleted RFC 2898), with HMAC as the PRF
- • Cost model: linear in the iteration count — doubling the iterations doubles the work. (bcrypt's cost factor is the exponential one; PBKDF2's is not, which is exactly why the recommended counts are in the hundreds of thousands.)
- • Implementation: the browser's native Web Crypto (
crypto.subtle.deriveBits) — the same code path the browser uses for TLS - • PRFs: HMAC-SHA-1, HMAC-SHA-256, HMAC-SHA-384, HMAC-SHA-512
- • Output length: 16–64 bytes; longer outputs cost proportionally more, because PBKDF2 runs the whole iteration chain once per output block
- • Salt: supplied as hex, base64 or UTF-8 text; 16 bytes minimum per NIST SP 800-132
- • Parsed formats: Django, passlib/PHC, Werkzeug, hash:salt hex, bare hex/base64 digests
- • Privacy: everything runs in your browser — no request is made, so no password or salt leaves the device. Check the DevTools Network tab if you want proof.
Deployment notes
crypto.timingSafeEqual, hmac.compare_digest), not ==.Common use cases
About PBKDF2 Hash Generator & Verifier
The PBKDF2 Hash Generator & Verifier derives PBKDF2-HMAC keys from a password and verifies passwords against hashes that were produced somewhere else. PBKDF2 is defined over bytes, not characters, so the tool asks you explicitly whether your salt is hex, base64 or UTF-8 text and shows the resulting byte length — the single most common reason a hash generated in one place refuses to verify in another. Verification accepts the formats real frameworks store: Django's pbkdf2_sha256$iterations$salt$base64, passlib's $pbkdf2-sha256$rounds$salt$digest, Werkzeug's pbkdf2:sha256:iterations$salt$hex, hash:salt in hex, and bare hex or base64 digests. Parameters are read out of the string, so you only supply the password.
Why use a PBKDF2 Hash Generator & Verifier?
Most PBKDF2 tools hash a salt as text while labelling the field 'hexadecimal', which quietly produces digests that no other language can reproduce. This one is byte-exact and says which bytes it used, so a value generated here matches Node's crypto.pbkdf2Sync, Python's hashlib.pbkdf2_hmac, Java's PBKDF2WithHmacSHA256 and .NET's Rfc2898DeriveBytes given the same inputs. It also defaults to the OWASP floor for the PRF you pick (600,000 for SHA-256), flags anything below it in red, benchmarks the actual derivation on your device, and tells you what iteration count would land in the 250-400 ms window on hardware like yours.
Who is it for?
Engineers debugging a login that will not authenticate, teams porting password hashes between Django, Flask, Rails, Node, Go or .NET, security reviewers checking whether a deployment's iteration count still meets OWASP guidance, and developers working under FIPS 140-2/140-3 or NIST SP 800-132 constraints where PBKDF2 is the approved KDF. It is also useful for anyone deriving an encryption key from a passphrase who needs to control the salt bytes exactly.
How to use the tool
Pick Generate Hash to derive a key, or Verify Hash to check a password against a stored hash
In Generate mode, type the password, then enter a salt and — this is the important part — choose the encoding it is written in: Hex, Base64 or Text (UTF-8). The byte count shown underneath is what PBKDF2 will actually consume
Click the refresh icon to generate a 16-byte random salt in the selected encoding (Text mode produces a Django-style 12-character salt)
Choose the PRF (SHA-256 is the default) — the iteration count follows the OWASP floor for that PRF automatically, and anything lower is flagged in red
Click Generate Hash and read the benchmark: it reports the milliseconds this derivation took on your device and the iteration count that would take 250-400 ms here
Switch the output between hex, base64, a ready-to-store Django string and a passlib/PHC string, and copy the one your stack expects
In Verify mode, paste the stored hash — the tool detects the format and fills in the PRF, iterations, salt and digest length for you
Type only the password and click Verify Password; a mismatch shows both digests side by side, and an unparseable string is reported as a parse error rather than as a wrong password
Key Features
Explicit salt encoding (hex / base64 / UTF-8 text)
PBKDF2 hashes bytes. '0102030405060708' is 8 bytes as hex and 16 bytes as text, and the two derive different keys. The encoding is a first-class selector on both generate and verify, and the decoded byte length is shown live so you always know what was hashed.
Pasted-hash verification with format detection
Recognises Django (pbkdf2_sha256$...), passlib/PHC ($pbkdf2-sha256$...), Werkzeug (pbkdf2:sha256:600000$...), hash:salt in hex, and bare hex/base64 digests. The PRF, iteration count, salt and digest length are filled in from the string — you supply only the password.
Wrong password vs unreadable string
A parse failure is reported as a parse failure, with the reason and the list of supported formats, instead of being collapsed into a red 'does not match'. Mismatches show the stored and computed digests so you can see how far off you are.
OWASP floors enforced in the UI
Defaults follow the OWASP Password Storage Cheat Sheet per PRF: 600,000 for SHA-256, 210,000 for SHA-512, 1,300,000 for SHA-1. Anything below the floor gets a red badge on the input and on the result.
Live benchmark on your own device
Every run reports its wall-clock time, the iterations-per-second it implies, and the iteration count that would land in the 250-400 ms target window on this hardware — with the reminder to re-measure on the server that will run the login endpoint.
Four output formats
Hex, standard base64, a Django auth_user.password string, and a passlib-compatible PHC string using adapted base64 ('.' for '+', no padding). Each is copyable; formats that cannot represent your parameters explain why instead of emitting something subtly wrong.
Iteration range up to 10,000,000
The cap is 10 million rather than 1 million, with a warning above 4 million. Invalid input is reported next to the field instead of being silently reset to a default.
100% client-side
Derivation uses the browser's native Web Crypto (crypto.subtle.deriveBits) — the same implementation the browser uses for TLS. No request is made, so no password, salt or digest leaves the device.
Common Use Cases
A login that refuses to authenticate
Scenario: Users cannot sign in after a migration, and you need to know whether the password, the stored hash or your verification code is wrong.
✓ Paste the stored hash and the password you believe is correct. A match proves the hash and password are fine and the bug is in your code path; a mismatch shows both digests so you can test the usual suspects — salt encoding, iteration count and PRF — one at a time.
Porting hashes between stacks
Scenario: You are moving a Django or Flask application to Node, Go or .NET and must keep every existing password working.
✓ Confirm that your new implementation produces exactly the bytes this tool produces for the same salt bytes, iteration count and PRF — then confirm the legacy strings still verify before you cut over.
Choosing an iteration count
Scenario: Your policy says 'as high as we can afford' and you need a defensible number rather than a copied one.
✓ Run the benchmark, read the iterations-per-second figure, scale it to your production CPU, and pick the value that lands a single derivation in the 250-400 ms window. Compare it against the OWASP floor shown in the tool.
Deriving an encryption key from a passphrase
Scenario: You need a 32-byte key for AES-256 or ChaCha20 from a user passphrase, with a salt you control exactly.
✓ Set the key length to 32 bytes, supply the salt in whatever encoding your storage uses, and copy the hex or base64 output — byte-identical to what your language's KDF will produce.
A FIPS-constrained deployment
Scenario: Government, regulated finance or healthcare work where the approved-algorithm list rules out Argon2id.
✓ PBKDF2 is the only one of the four modern password hashes approved under NIST SP 800-132 and FIPS 140-2/140-3. Use the tool to document the parameters you chose and the measured cost behind them.
Byte-for-byte identical to crypto.pbkdf2Sync, hashlib.pbkdf2_hmac and PBKDF2WithHmacSHA256 for the same inputs
100% client-side — passwords, salts and digests never leave your browser
Static page — no backend dependency
Frequently Asked Questions
How do I verify a password against a Django, Flask or passlib PBKDF2 hash?
Switch to Verify mode and paste the whole stored string. The tool recognises Django's pbkdf2_sha256$600000$salt$base64, passlib's $pbkdf2-sha256$29000$salt$digest, Werkzeug's pbkdf2:sha256:600000$salt$hex, hash:salt in hex, and bare hex or base64 digests. It reads the PRF, iteration count, salt and digest length out of the string and shows them, so the only thing you type is the password. Django and Werkzeug store the salt as literal text and hash its UTF-8 bytes; passlib stores it in adapted base64. The tool handles each convention correctly rather than assuming one.
How many PBKDF2 iterations should I use in 2026?
The OWASP Password Storage Cheat Sheet floors are 600,000 for PBKDF2-HMAC-SHA256, 210,000 for SHA-512 and 1,300,000 for SHA-1. Those are minimums, not targets. The real target is wall-clock cost: tune until one derivation takes 250-400 ms on the hardware that will run your login endpoint. Anything under 100 ms makes offline cracking cheap; anything over about a second turns your login route into a denial-of-service lever. This tool defaults to the floor for whichever PRF you select, flags lower values in red, and reports what the measured speed on your device implies.
Is it safe to paste a password or a password hash into this tool?
The derivation runs entirely in your browser through the Web Crypto API — the same implementation the browser uses for TLS. No HTTP request is made during generation or verification, so nothing you type is transmitted or logged; open the DevTools Network tab and you will see zero requests. The usual caution still applies: a stored hash is sensitive material, so treat it as you would any credential, and prefer a machine you control. If your threat model excludes browsers entirely, reproduce the same parameters locally with Python's hashlib.pbkdf2_hmac or Node's crypto.pbkdf2Sync.
PBKDF2, bcrypt or Argon2id — which should I choose?
Argon2id first for anything new: it is memory-hard, so an attacker has to buy RAM as well as compute, and OWASP lists it as the default recommendation. bcrypt at cost 12 or above remains sound and is fine to keep. PBKDF2 is the right answer in exactly one situation — when FIPS 140-2/140-3 or NIST SP 800-132 compliance limits you to approved algorithms, because it is the only one of the three on that list. PBKDF2 is not broken; it is simply the easiest of the three to attack with GPUs, since each iteration is one cheap HMAC with no memory cost.
Why does my PBKDF2 hash not match the one my backend produced?
Salt encoding is the usual cause. PBKDF2 hashes bytes, and '0102030405060708' is 8 bytes as hex but 16 bytes as text — two different keys from the same-looking salt. Check, in this order: the salt encoding, the iteration count, the PRF, the requested output length, and how the password itself was encoded (UTF-8 is near-universal, but some legacy stacks used UTF-16LE or Latin-1). Django and Werkzeug apply their salts as UTF-8 text; passlib decodes adapted base64 to raw bytes. This tool shows the decoded byte length next to the salt field so you can confirm before you compare digests.
What is actually inside a Django, PHC or Werkzeug PBKDF2 string?
Django writes algorithm$iterations$salt$digest, for example pbkdf2_sha256$600000$vJ2kQ8mZr4Tx$ke067F...= — the salt is 12 literal alphanumeric characters and the digest is standard padded base64 of 32 bytes. passlib writes $pbkdf2-sha256$rounds$salt$digest where both the salt and the digest use adapted base64: '.' instead of '+', with the '=' padding removed. Werkzeug writes pbkdf2:sha256:600000$salt$digest with a text salt and a hex digest; versions before 2.3 omitted the iteration count entirely. All three are self-describing, which is what lets you raise the cost later without forcing a password reset.
Is PBKDF2's cost exponential like bcrypt's cost factor?
No, and the confusion is expensive. PBKDF2's iteration count is linear: 600,000 iterations cost exactly twice 300,000. bcrypt's cost factor is the exponential one — cost 12 means 2^12 = 4,096 internal rounds, so each +1 doubles the work. That difference is why PBKDF2's recommended numbers look enormous next to bcrypt's: each PBKDF2 iteration is a single HMAC, which is cheap, so you need hundreds of thousands of them to reach the same wall-clock cost as bcrypt cost 12. Never compare the two by iteration count; compare measured milliseconds per hash.
What salt and output length should PBKDF2 use?
16 bytes (128 bits) of CSPRNG output per password is the NIST SP 800-132 minimum, generated fresh for every user and stored next to the digest — never reused, never derived from the username. For the output, 32 bytes matches SHA-256 and is the sensible default for password storage; for key derivation, match the downstream key size (32 bytes for AES-256 or ChaCha20, 16 for AES-128). Longer outputs cost proportionally more because PBKDF2 reruns the whole iteration chain for each output block, and truncating below the PRF size buys nothing — derive 32 bytes and use the first 16 instead. Not sure what algorithm produced a hash you were handed? Run it through the Hash Identifier first.
Technical Specifications
Supported Formats
- ✓Algorithm: PBKDF2 per RFC 8018 (PKCS #5 v2.1), HMAC as the PRF
- ✓PRFs: HMAC-SHA-1, HMAC-SHA-256, HMAC-SHA-384, HMAC-SHA-512
- ✓Salt input encodings: hexadecimal, standard base64, UTF-8 text
- ✓Output formats: hex, standard base64, Django (pbkdf2_sha256$...), passlib/PHC ($pbkdf2-sha256$...)
- ✓Parsed input formats: Django, passlib/PHC, Werkzeug/Flask, hash:salt hex, bare hex or base64 digest
- ✓Output lengths: 16, 24, 32, 48 and 64 bytes
- ✓Iterations: 1 to 10,000,000, with a warning above 4,000,000
- ✓Implementation: crypto.subtle.deriveBits (Web Crypto), no third-party crypto library
Limits & Performance
- •File Size: Text input only — no file upload
- •Validations: Salt bytes, iteration count and hash format are validated live, with the reason shown next to the field
- •Response Time: Roughly 250-500 ms for 600,000 SHA-256 iterations on a modern laptop; the exact figure for your device is reported after every run
- •Browsers: Any browser with Web Crypto (Chrome, Firefox, Safari, Edge); works offline once the page has loaded
Pro Tips
- If a hash from another system will not verify, change the salt encoding before you doubt the password — hex versus text is the most common mismatch by a wide margin.
- Benchmark on the machine that will run your login endpoint. A laptop with a fast single-core is not a shared 2 vCPU container, and the difference is routinely 3-5x.
- Store a self-describing string (the Django or PHC output from this tool) rather than four separate columns; raising the iteration count later then becomes a rehash-on-login, not a migration.
- Rehash inside the successful-login branch when the stored parameters are below your current policy — it is the only moment you legitimately hold the plaintext password.
- PBKDF2-SHA512 buys the same work factor at roughly a third of the iterations because each iteration does more on 64-bit CPUs; it is a reasonable choice when your library and your peers both support it.
- Do not truncate a derived key below the PRF's output size. Derive the full 32 bytes and slice, rather than asking PBKDF2 for 16.
- On the server, compare digests with crypto.timingSafeEqual or hmac.compare_digest, never with ==; a naive comparison leaks position information through timing.
- A pepper lives outside the database (ideally in a KMS or HSM) and applies to every password. It complements a per-user salt — it never replaces one.
Share This Tool
Found this tool helpful? Share it with others who might benefit from it!
💡 Help others discover useful tools! Sharing helps us keep these tools free and accessible to everyone.