Your reset failed because user_pass is not an MD5
Someone locked out of wp-admin opens phpMyAdmin, sees the MD5 dropdown next to the password field, picks it, saves, and still cannot log in. That dropdown writes a 32 character digest. WordPress has not accepted a bare MD5 as its storage format since version 2.5 in 2008.
What sits in that column is a phpass portable hash: 34 characters starting with $P$, holding a salt and 8192 rounds of MD5 folded over each other. Nothing in phpMyAdmin produces it. MySQL has no function for it either.
The console above does produce it, and writes the UPDATE statement to go with it.
What the 34 characters actually hold
Take a real hash and split it: $P$B12345678Eg2z6eqcenWjcSosVt4mZ.
- $P$The phpass portable marker. Solar Designer wrote the library, and WordPress ships it in
wp-includes/class-phpass.php. phpBB3 uses the same algorithm behind$H$, so the two are interchangeable once you swap the prefix. - BOne character encoding the round count as a power of two. B sits at index 13 in the phpass alphabet, giving 2^13, or 8192 rounds. WordPress asks for cost 8 and phpass adds 5 on PHP 5 and later, which is where 13 comes from.
- 12345678Eight characters of salt, stored in the clear because it has to be readable to check the password later. Its work is stopping one rainbow table from covering every account.
- Eg2z...mZ.Twenty two characters holding the 16 byte digest, packed six bits per character in the phpass alphabet. Close to base64 but not the same alphabet, so a standard base64 decoder returns nonsense.
Two accounts with the same password get different hashes, because the salt differs. That is the point of the salt, and it is also why you cannot copy one user's hash to another user and reason about it as a shared value.
Getting back into a site you own
- Try the email reset first. The database route is for cases where mail delivery is broken or the address on the account is dead. Editing rows by hand when you have a working reset link is risk with no return.
- Back up the users table. One
mysqldumpofwp_userstakes a second and undoes any mistake in the next four steps. - Generate the hash above and set the table prefix to match your install. The default is
wp_, but a hardened site often uses something random, and the statement fails loudly against the wrong name. - Run the UPDATE in phpMyAdmin, Adminer, or the mysql client. Confirm it reports one row changed. Zero rows means the username did not match, and the WHERE clause is case sensitive on most collations.
- Log in, then change the password from the profile screen. A password that passed through a browser tool, a clipboard, and a database client has too many copies to keep. WordPress rewrites the row with a fresh salt as soon as you set it properly.
Hosting with WP-CLI available skips all of this: wp user update admin --user_pass="new-password" hashes the value through WordPress itself. Use that when you have shell access.
WordPress 6.8 changed the format, and old hashes still work
Since WordPress 6.8, new passwords are stored with bcrypt at cost 10, prefixed $wp$2y$. The password is run through HMAC-SHA-384 and base64 encoded before bcrypt sees it, which sidesteps the 72 byte input limit bcrypt has always carried.
Existing $P$ hashes were not migrated. WordPress checks them exactly as before and rewrites the row in the new format the next time that user logs in. A phpass hash written today therefore works on 6.8 and on 6.9, and gets replaced on first use rather than rejected.
Generating bcrypt in a browser tab is slow enough to be unpleasant, so this page stops at phpass. If your install is on 6.8 or later and you want the native format, run wp_hash_password() through WP-CLI or a small PHP script on the server.
Reading a prefix you were handed
- 32 hex characters, no prefix
- Bare MD5 from WordPress 2.4 or earlier, or something a plugin or a tutorial wrote by hand.
wp_check_password()still accepts these and upgrades the row on the next successful login. The Check tab tests this format too. - $P$B
- Standard phpass from WordPress 2.5 through 6.7, and still the value a 6.8 site holds for anyone who has not logged in since the upgrade. 8192 rounds.
- $P$ with another letter
- Same algorithm at a different round count. Software other than WordPress sets its own cost, and phpass reads the count out of the hash, so a value written at a different cost still validates.
- $H$
- phpBB3. Byte for byte the same construction. Change the prefix to
$P$and WordPress accepts it, which is how most forum to WordPress migrations carry passwords across. - $wp$2y$10$
- WordPress 6.8 or later, bcrypt with the HMAC-SHA-384 pre-hash. Not checkable here.
- $2y$10$
- Plain bcrypt with no WordPress wrapper. Usually a custom login plugin or a framework sharing the same user table.
Rounds, and why raising them is mostly pointless
The slider goes to 2^17 because the format allows it. Setting it there is close to a waste of time.
8192 rounds of MD5 cost a modern GPU almost nothing. Hashcat mode 400 covers phpass at hundreds of millions of guesses per second on consumer hardware. Doubling the rounds halves the attacker's rate and does the same to your login latency, and you would need roughly a thousandfold increase to reach what bcrypt at cost 10 gives you for free.
There is a practical catch as well. WordPress hardcodes cost 8 in wp_hash_password(), so any row you write at a different count gets rewritten to the default the moment that user logs in. The higher settings on the slider exist for reproducing hashes from other phpass software, not for hardening a WordPress install.
If password storage strength is the real concern, the fix is upgrading to 6.8 for bcrypt, not turning a dial on MD5.
Where this page stops
No cracking. The Check tab confirms one password against one hash, which is what you need to test a migration or confirm a reset worked. Feeding it a candidate list is not something it does.
No bcrypt, in either direction. Neither $wp$2y$ nor $2y$ hashes are generated or verified here. The Check tab identifies them and tells you so rather than reporting a false mismatch.
No file hashing. Earlier versions of this page accepted file uploads, which made no sense for a password field and produced values WordPress would never accept. That path is gone. For file digests use the MD5 Hash Generator or the Hash Generator Suite.
No application passwords. Those live in wp_usermeta, not user_pass, and WordPress hashes them with a fast unsalted scheme because they are long random strings rather than chosen passwords. Different field, different rules.
One accuracy note on the salt: the eight characters come from crypto.getRandomValues where the browser provides it, falling back to Math.random on anything ancient enough to lack it. Any browser from the last decade takes the first path, but the fallback is worth knowing about if you are running something unusual.
