Through the 2010s a great many applications ran the same migration. The users table held 32-character MD5 digests, MD5 was in the news as broken, and the fix looked obvious: re-hash each password with SHA-256 as its owner next logged in, widen the column to 64 characters, deploy. The digests got longer. The column got wider. The attacker's job did not change.

Two errors are folded into that migration, and they point in opposite directions. What broke in MD5 was collision resistance — the guarantee that nobody can produce two different inputs with the same digest. Password storage never rested on that guarantee. And what makes SHA-256 a poor place to keep a password is not a weakness at all. It is speed, and MD5 has exactly as much of it.

The fix was not a better hash, but a different category of function entirely. We'll separate the two properties hidden by the word "broken", price the published attacks in the researchers' own figures, and end with a table of which algorithm belongs on which job. Everything measured here was run on 25 July 2026 in throwaway containers on one Apple Silicon laptop — single core, interpreted Python and PHP, the weakest attacker anyone could field. The transcripts appear exactly as they came out.

2^123.4Complexity of the best published MD5 preimage attack. RFC 6151 §2.2 calls it "still pretty high"
2^63.1SHA-1 compressions spent on the first SHA-1 collision — SHAttered, 2017. A SHA-1 preimage, which nobody has reached, costs 2^160
3,211,254/sSHA-256 digests on one interpreted-Python core. MD5 on the same core: 2,985,993 — indistinguishable
544,280×How much slower bcrypt at cost 12 is than one raw SHA-256 pass — a Python-loop SHA-256 rate against PHP's bcrypt, same machine, same core
15 charactersNIST SP 800-63B-4 §3.1.1.2 minimum for a password used as a single authentication factor

A collision is not a preimage

Two different attacks are described with the same word, and the distinction is critical.

A collision, in the definition the SHAttered authors use, is "two distinct messages x, y that hash to the same value H(x) = H(y)". The attacker builds both messages. He controls both. Nobody else's data enters the picture.

A preimage attack runs the other way: given a digest, find an input that produces it. That is the attack you would need to read a password out of a stored digest, and it is the attack nobody has performed. Against MD5 the best published result still costs 2^123.4 operations. For SHA-1 there is no published shortcut to quote at all — only brute force, at 2^160.

RFC 6151, the IETF's 2011 security update on MD5, states the position plainly in §2.2: "Even though the best result can find a pre-image attack of MD5 faster than exhaustive search… the complexity 2^123.4 is still pretty high." The same document, in §2, is unsparing about the other property: "MD5 is no longer acceptable where collision resistance is required such as digital signatures." Both statements are true, because they address different properties.

Transcript 13 — collision vs preimage, the arithmetic
A COLLISION IS NOT A PREIMAGE. The numbers, for the algorithms our tool offers.

algo         bits birthday bound   best collision     preimage (brute)
                n        2^(n/2)        published                  2^n
MD5           128           2^64  2^~24 (seconds)                2^128
SHA-1         160           2^80 2^63.1 (SHAttere                2^160
SHA-256       256          2^128   none published                2^256
SHA-512       512          2^256   none published                2^512

What the published SHA-1 breaks actually cost, vs what they buy:
  SHAttered 2017 (identical-prefix): 2^63.1 SHA-1 compressions
     = 9,885,365,384,200,275,968 SHA-1 operations
     paper: '6 500 CPU years and 100 GPU years', 'more than 100 000 times faster than a brute force search'
  Leurent-Peyrin 2020 (chosen-prefix): 2^63.4, ~900 GPUs x 2 months, ~USD 45k-75k

What NONE of them buy - a preimage on SHA-1 (recover ANY input for a GIVEN digest):
  brute-force preimage cost = 2^160 = 1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976 SHA-1 operations
  ratio preimage / SHAttered-collision = 2^(160-63.1) = 2^96.9 = 1.478e+29 times more work

And for MD5, the best PUBLISHED preimage attack (RFC 6151 sec 2.2):
  complexity 2^123.4 = 14,031,414,845,272,022,750,773,861,830,619,037,696 operations - RFC 6151: 'still pretty high'
  vs an MD5 COLLISION, findable on a laptop in seconds.

=> 'MD5 is broken' and 'MD5 reveals your password' are different claims.
   The first is true. The second is false - what reveals your password is that
   MD5 is FAST, so the attacker guesses forward, and never has to invert anything.
A single log-base-2 axis of attack cost. On the left, three demonstrated collisions: MD5 at 2^~24, an order-of-magnitude estimate, the SHAttered SHA-1 collision at 2^63.1 and the Leurent-Peyrin chosen-prefix collision at 2^63.4. On the right, two preimage costs that have never been reached: MD5's best published preimage at 2^123.4 and a brute-force SHA-1 preimage at 2^160, with the gap between the SHAttered collision and the SHA-1 preimage marked as 2^96.9, about 1.48 times 10 to the 29th, times more work
Every published break sits on the left. The attack that would actually read a password out of a digest sits on the right, and nobody has been there. Costs from transcript 13, sourced to the SHAttered paper, SHA-mbles and RFC 6151 §2.2.

The gap in the middle is the whole argument. Going from the SHAttered collision to a SHA-1 preimage is 2^96.9 more work — about 1.48 × 10²⁹ times as much. (The SHA-1 row in the transcript above reads SHAttere because the script's column width cut the word off; it is reproduced as printed, like everything else here.)

What actually broke, and what it cost

The researchers behind the 2017 result were precise about what they had and had not achieved. From the abstract of "The first collision for full SHA-1", by Marc Stevens, Elie Bursztein, Pierre Karpman, Ange Albertini and Yarik Markov of CWI Amsterdam and Google Research: "In this paper, we demonstrate that SHA-1 collision attacks have finally become practical by providing the first known instance of a collision. Furthermore, the prefix of the colliding messages was carefully chosen so that they allow an attacker to forge two PDF documents with the same SHA-1 hash yet that display arbitrarily-chosen distinct visual contents."

The price: "In total the computational effort spent is equivalent to 2^63.1 SHA-1 compressions and took approximately 6 500 CPU years and 100 GPU years. As a result while the computational power spent on this collision is larger than other public cryptanalytic computations, it is still more than 100 000 times faster than a brute force search."

Read the first quote again for what it does not say. The attackers chose the shared prefix. They produced both PDFs. This is an identical-prefix collision — a pair of files built to order, not a key that opens someone else's digest.

Three years later Gaëtan Leurent of INRIA and Thomas Peyrin of NTU produced the harder version, "SHA-1 is a Shambles", and their definition of the target shows why it is harder: "two message prefixes P and P' are first given as challenge to the adversary, and his goal is then to compute two messages M and M' such that H(P || M) = H(P' || M')." The adversary no longer picks the beginning. They brought the cost down from 2^67.1 to 2^63.4 on a GTX 970, ran it on about 900 Nvidia GTX 1060 GPUs over roughly two months for about USD 75,000, and estimated the same attack would cost about 45,000 dollars at the time of writing. Then they aimed it at PGP: they created two PGP keys with different UserIDs and colliding certificates, so that "a SHA-1 certification of the first key can therefore be transferred to the second key". Chosen-prefix collisions, they note, "can break signature schemes and handshake security in secure channel protocols (TLS, SSH)" in ways identical-prefix collisions cannot.

MD5 travelled the same road earlier and faster. The SHAttered paper's own introduction records the milestones: a brute-force collision hunt "was halted early in 2004, when a team of researchers led by Xiaoyun Wang [43] demonstrated collisions for MD5 found by a groundbreaking special cryptanalytic attack", and a later chosen-prefix attack "eventually led to the forgery of a Rogue Certification Authority that in principle completely undermined HTTPS security [39] in 2008". The same abstract notes that SHA-1 "was officially deprecated by NIST in 2011". These are serious, load-bearing breaks. Every one of them is a collision.

The property that emptied the password tables

Set the collisions aside. A leaked MD5 password table is cracked over a weekend because of raw speed, not cryptanalysis.

Transcript 03 — four algorithms, one core
python 3.12.13 | openssl-backed hashlib
md5        300000 digests in   0.100s  ->     2,985,993 hashes/sec (1 core, python)
sha1       300000 digests in   0.094s  ->     3,183,590 hashes/sec (1 core, python)
sha256     300000 digests in   0.093s  ->     3,211,254 hashes/sec (1 core, python)
sha512     300000 digests in   0.111s  ->     2,691,605 hashes/sec (1 core, python)

The four common algorithms are effectively the same speed. Two-point-seven to three-point-two million digests a second, on one core, in an interpreted language. The MD5-to-SHA-256 migration moved a password table from 2,985,993 guesses per second to 3,211,254 guesses per second, in the attacker's favour.

One honest wrinkle in that table: SHA-512 came out slower than SHA-256 here, at 2,691,605 against 3,211,254, which contradicts the usual line that SHA-512 is faster on 64-bit processors. The input was 28 bytes. At that size you are paying per-block overhead rather than throughput, so the ordering depends on message length. Worth knowing before quoting either version of the claim as a law.

Now change the category of function rather than the algorithm. Transcript 04 ran a single raw SHA-256 pass at 4,810,303.8 operations per second on the same core, and the deliberately slow alternatives at 16.2 (PBKDF2-HMAC-SHA256 at 600,000 iterations) and 36.8 (scrypt with a 16 MB working set) — slowdowns of 297,159× and 130,826× respectively. That 4,810,303.8 sits about half again above transcript 03's 3,211,254 for the same algorithm on the same core: a different loop over a different input, which is why the ratios worth quoting are the ones computed inside a single run. Transcript 09 consolidates the whole ladder and then does the only division that matters.

Transcript 09 — the guessing-cost table
algorithm                    guesses/sec (1 core)   slowdown vs SHA-256
MD5 (raw)                             2,985,993.0                    1x
SHA-1 (raw)                           3,183,590.0                    1x
SHA-256 (raw)                         3,211,254.0                    1x
SHA-512 (raw)                         2,691,605.0                    1x
PBKDF2-SHA256 10k                           965.8                3,325x
scrypt n=2^14 r=8 p=1                        36.8               87,262x
PBKDF2-SHA256 600k                           16.2              198,226x
bcrypt cost=10                               23.9              134,362x
bcrypt cost=12                                5.9              544,280x
bcrypt cost=14                                1.4            2,293,753x

TIME TO EXHAUST A SEARCH SPACE, at these measured single-core rates:
(entropy figures from transcript 07 - our own generator's charsets)
  8 chars,  pool 88 (our slider MINIMUM)    51.68 bits
      vs SHA-256 raw                                  36 years (avg = half that)
      vs bcrypt cost=12                       19,376,449 years (avg = half that)
  12 chars, pool 88                         77.51 bits
      vs SHA-256 raw                       2,123,519,916 years (avg = half that)
      vs bcrypt cost=12            1,155,790,139,975,259 years (avg = half that)
  16 chars, pool 88 (our slider DEFAULT)   103.35 bits
      vs SHA-256 raw             127,547,346,084,883,168 years (avg = half that)
      vs bcrypt cost=12   69,421,512,763,468,712,771,584 years (avg = half that)

NOTE: one core of interpreted python is the WEAKEST possible attacker.
      Scale by ~10^5-10^6 for a real GPU rig against a raw hash;
      bcrypt/scrypt/Argon2 scale far worse for the attacker by design.

Two notes on reading that block. Its raw-hash, PBKDF2 and scrypt rows come from the Python runs, its bcrypt rows from the PHP one, and the slowdown column divides everything by the Python SHA-256 rate — so every bcrypt figure in it, 544,280× included, is a ratio across two runtimes on the same core. And the label on the 16-character row, "our slider DEFAULT", was accurate when the run captured it that morning: our password generator's default length moved from 16 to 20 later the same day, while the minimum of 8 is unchanged. The transcript stands as printed.

Log-scale bar chart of guesses per second on a single CPU core. MD5 at 2,985,993, SHA-1 at 3,183,590, SHA-256 at 3,211,254 and SHA-512 at 2,691,605 form a group of four bars of near-identical length. Below them, password hashing schemes: PBKDF2-SHA256 at 10k iterations at 965.8, scrypt at 36.8, bcrypt cost 10 at 23.9, PBKDF2-SHA256 at 600k iterations at 16.2, bcrypt cost 12 at 5.9 and bcrypt cost 14 at 1.4
The top four bars are the choice everyone argues about, and on a log scale they are the same length. The distance down to the bottom group runs from three to six orders of magnitude. One core, interpreted Python and PHP, Apple Silicon, 25 July 2026 — a real GPU rig is faster by another five or six orders against the fast hashes, and scales far worse for the attacker against the slow ones.

Note where the 36-year figure sits. An 8-character password from the full 88-character pool carries 51.68 bits and survives 36 years of exhaustive search against a raw SHA-256 — but only at this deliberately feeble rate. Scale the attacker up by the five or six orders of magnitude the transcript's own note allows for a GPU rig, and 36 years collapses to hours — minutes at the top of that range. Against bcrypt at cost 12 the same password takes 19,376,449 years, and the GPU buys much less, because "bcrypt/scrypt/Argon2 scale far worse for the attacker by design". OWASP's Password Storage Cheat Sheet compresses all of this into one line: "Fast hashing algorithms such as SHA-256 are not suitable for password storage because they allow attackers to perform large numbers of guesses quickly."

What a salt does, and the sentence people stop at

Salting is the part of the folk advice that is correct, but it's usually quoted one sentence short.

Transcript 05 — what a salt actually does
PART A — three users, IDENTICAL password, NO salt:
  alice  sha256("hunter2") = f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
  bob    sha256("hunter2") = f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
  carol  sha256("hunter2") = f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7
  => all three digests are IDENTICAL. One cracked = all three cracked.

PART B — same three users, SAME password, DIFFERENT per-user salt:
  salt=3f9a1c7e2b48d05a  sha256(salt||pw) = b36cabf59f0e59e9cf625cf1a2a2f5e9b5cd98e0d10ffc3b7b7ea7ea7e9912a9
  salt=9d2e4f8a1c6b70e3  sha256(salt||pw) = 630c2c881af8b70fefe10867e0203b53d75592907d29be8b7f0e8f55563364b6
  salt=c17b5e0a94d3f286  sha256(salt||pw) = 69b6fff300e5b785b346b3944487d27e7c53af268b40a67e92e2ab5ce95c60c5
  => three DIFFERENT digests from one password. No shared precomputed table can hit all three.

PART C — the limit of salting: the salt is stored in the clear alongside the hash.
         An attacker who has the salt can still guess this ONE user at full speed.
         Salting defeats PRECOMPUTATION (rainbow tables). It does not slow guessing down.
         Cracking 3 salted users costs 3x one user - not 3x infinity.

The last sentence is the one people drop. A salt kills the precomputed table; it does not change the cost of a guess. Against a fast hash, the attacker simply runs the same three million guesses a second at each salt in turn. That is why "SHA-256 plus a salt" was never the answer, and why NIST specifies a salt and something else, not a salt instead of something else. SP 800-63B-4 §3.1.1.2 is exact about the salt itself: "The salt SHALL be at least 32 bits in length and chosen to minimize salt value collisions among stored hashes."

The same section asks for a second secret. NIST: "Verifiers SHOULD perform an additional iteration of a keyed hashing or encryption operation using a secret key known only to the verifier." OWASP calls that value a pepper and marks the distinction clearly — "A pepper is shared between stored passwords, rather than being unique to an individual password like a password salt." The salt goes in the database. The pepper does not.

What the standard actually asks for

NIST published the final Revision 4 of SP 800-63B in July 2025; the March 2020 revision now carries a withdrawal notice. Section 3.1.1.2 on password verifiers contains a requirement that's often quoted incomplete; its operative sentence names a category of function, not a specific algorithm.

"Verifiers SHALL store passwords in a form that is resistant to offline attacks." Then the operative sentence: "Passwords SHALL be salted and hashed using a suitable password hashing scheme." A scheme, not a hash. The point is reinforced two clauses later: "The chosen cost factor SHOULD be as high as practical without negatively impacting verifier performance", and "It SHOULD be increased over time to account for increases in computing performance."

A bare SHA-256 has no cost factor to set high or increase over time. It fails the NIST requirement structurally, because the requirement is written about a knob it does not have.

Revision 4 stops short of naming iteration counts, pointing instead at "An approved password hashing scheme published in the latest revision of [SP800-132] or updated NIST guidelines on password hashing schemes". For concrete parameters, two documents are worth having open. RFC 9106 §4 gives Argon2's: "select Argon2id with t=1 iteration, p=4 lanes, m=2^(21) (2 GiB of RAM), 128-bit salt, and 256-bit tag size. This is the FIRST RECOMMENDED option", with a smaller-memory fallback of "t=3 iterations, p=4 lanes, m=2^(16) (64 MiB of RAM)" as the second, and a tiebreaker for anyone unsure which variant to pick: "If you do not know the difference between the types or you consider side-channel attacks to be a viable threat, choose Argon2id." OWASP supplies the rest: PBKDF2-HMAC-SHA256 at 600,000 iterations, PBKDF2-HMAC-SHA512 at 220,000, PBKDF2-HMAC-SHA1 at 1,400,000 for legacy use only, and for bcrypt, "The work factor should be as large as verification server performance will allow, with a minimum of 10."

One bcrypt footnote that bites passphrase users: OWASP warns that "bcrypt has a maximum length input length of 72 bytes [for most implementations], so you should enforce a maximum password length of 72 bytes." Silent truncation past that point is worse than a rejection.

The same NIST section rewrites the composition rules while it is there. Passwords used as a single factor SHALL be "a minimum of 15 characters in length"; verifiers SHOULD "permit a maximum password length of at least 64 characters"; and they "SHALL NOT impose other composition rules (e.g., requiring mixtures of different character types)" or "require subscribers to change passwords periodically". The one-uppercase-one-symbol box is not merely unhelpful now. Requiring it is forbidden.

Cost is an exponent

"As high as practical" is a concrete instruction once you see how the cost factor scales.

Transcript 06 — bcrypt cost factor
PHP 8.4.23 | password_hash(), PASSWORD_BCRYPT

PART A — same password, same cost, run three times:
  $2y$10$yKp7FTVpbF7ZLFelnY2oYO8nmW1MMDYt1MrV1BAM3TZjU8d3nFp1O
  $2y$10$xjqF83S9RLA6BheX1KSoveCDkoTBbTD5VK/VeYbalr5C9kL.OXKG2
  $2y$10$lXpo00rFxRTEUF8AqUAHheub15qv.XkCx1gqVjjXl6AbCRNen2.Q.
  => three DIFFERENT strings. bcrypt salts automatically; the salt is IN the output.
  => format: $2y$COST$[22-char salt][31-char hash]

PART B — cost factor vs wall-clock time (one hash each):
  cost=8      10.51 ms/hash  ->        95.2 hashes/sec (1 core)
  cost=10     41.85 ms/hash  ->        23.9 hashes/sec (1 core)
  cost=12    169.53 ms/hash  ->         5.9 hashes/sec (1 core)
  cost=14    702.15 ms/hash  ->         1.4 hashes/sec (1 core)
  => each +1 to cost DOUBLES the work. cost is a log2 exponent, not a multiplier.

From 10.51 ms to 702.15 ms across six increments. Each step doubles. That is what makes the NIST instruction a maintenance task rather than a setting: hardware gets faster, the attacker's guess rate rises with it, and the only way to hold the defence steady is to add one to the cost every few years. RECATOOLS runs bcrypt at cost 12 — 169.53 ms and 5.9 hashes per second on one core, which is the middle of that table and comfortably above OWASP's floor of 10.

Part A demonstrates another feature: hashing the same password twice at the same cost returns two different strings. This is because bcrypt generates a fresh salt for each call and writes it into the output: $2y$COST$[22-char salt][31-char hash]. A stored bcrypt hash is not a digest. It is a small record containing the algorithm, the cost, the salt and the result — everything a verifier needs, and nothing an attacker can precompute against.

Which hash for what

NIST's own transition document draws the line this guide is built on. SP 800-131A Revision 2, from March 2019 and still the current final revision — Revision 3 remains an initial public draft, and the URL for a final Rev. 3 returned a 404 when we checked on 25 July 2026 — sets out SHA-1's status in three parts. Digital signature generation: "SHA-1 may only be used for digital signature generation where specifically allowed by NIST protocol-specific guidance. For all other applications, SHA-1 is disallowed for digital signature generation." Verification: "When used for digital signature verification, SHA-1 is allowed for legacy use." The key sentence for other uses is this: "For non-digital-signature applications, the use of SHA-1 is acceptable for applications that do not require collision resistance."

Fitness depends on the job, a point NIST makes explicit. That said, the direction of travel is clear enough. NIST's December 2022 news release announced that "The SHA-1 algorithm, one of the first widely used methods of protecting electronic information, has reached the end of its useful life", because "Today's more powerful computers can create fraudulent messages that result in the same hash as the original" — a collision, described precisely. Chris Celi's advice in the same release: "We recommend that anyone relying on SHA-1 for security migrate to SHA-2 or SHA-3 as soon as possible." The CSRC announcement puts a date on it: "NIST will transition away from the use of SHA-1 for applying cryptographic protection to all applications by December 31, 2030."

JobWhat to useWhy
File checksums, download verification, cache keysSHA-256Collision resistance is not strictly needed when nobody is choosing both inputs — the SP 800-131A line explicitly permits SHA-1 here. But SHA-256 ran at the same speed on our test machine, so there is no reason to spend the caveat.
Content addressing and deduplication where an attacker can supply the contentSHA-256Now both inputs are attacker-chosen, which is exactly the SHAttered setting: two PDFs, one SHA-1 digest, "arbitrarily-chosen distinct visual contents". MD5 and SHA-1 are out.
Message authentication, webhook signaturesHMAC-SHA-256RFC 6151 §2.3 notes that "The attacks on HMAC-MD5 do not seem to indicate a practical vulnerability when used as a message authentication code" — but §2 still says "new protocol designs should not employ HMAC-MD5". Use a SHA-2 key, and compare in constant time.
Digital signatures and certificatesSHA-2 or SHA-3 onlyMD5 "is no longer acceptable where collision resistance is required such as digital signatures" (RFC 6151 §2); SHA-1 generation is disallowed and verification is legacy use only, with the sunset set at 31 December 2030.
Storing a passwordNone of the aboveArgon2id, bcrypt, scrypt or PBKDF2 — a scheme with a cost factor, salted, per NIST SP 800-63B-4 §3.1.1.2. A general-purpose hash fails here no matter which one you pick, and adding a salt does not change that.

Our own two pages, marked against this

Our hash generator computes MD5, SHA-1, SHA-256 and SHA-512 simultaneously as you type, plus HMAC-SHA-256 with a key you supply. Three of those four come from the browser's own Web Crypto API; MD5 comes from a JavaScript implementation bundled with the page, because Web Crypto deliberately omits it. Nothing you type is uploaded — file mode reads with FileReader and hashes locally too. The fact that all four digests appear on the same keystroke demonstrates the point: these functions are free, which is exactly why they're wrong for the one job people keep giving them.

Until earlier today the same page carried a fact card on rainbow tables that ended "SHA-256 + salt defeats them". Read narrowly that is true — a salt does retire the precomputed table, which is what the card was about. The fault was that nothing followed it, so a sentence about precomputation read as general advice for password storage. This was on a page whose own FAQ already said that only a deliberately slow scheme works. It has been rewritten. The card now separates the two — the salt kills the table, the slowness kills the guessing — and points at "a deliberately slow, memory-hard KDF: Argon2id … bcrypt, scrypt or PBKDF2", where it should have pointed all along.

Our password generator was repaired the same day. Its randomness was never the problem — it calls window.crypto.getRandomValues with rejection sampling to remove modulo bias, and Math.random() appears nowhere in the file. The strength meter was the problem: it used to award points for character variety, the very mixture NIST now forbids verifiers from requiring. It now reports entropy in bits, computed as length × log₂(pool size), and bands the result: under 50 Weak, 50 to under 70 Fair, 70 to under 90 Good, 90 to under 120 Strong, 120 and above Very Strong.

The arithmetic that forced the change is easy to check. The full pool is 88 characters — 26 upper, 26 lower, 10 digits, 26 symbols — so 16 characters from all four sets is 16 × log₂(88) = 103.35 bits. Sixty-four lowercase characters, which look far weaker on any variety scale, is 64 × log₂(26) = 300.83 bits. The old meter called the first "Very Strong" and the second "Good". It had the ordering backwards; length is the term that dominates.

FAQ

Is MD5 still acceptable for file checksums?

For catching accidental corruption on a download, yes, and NIST's own transition document permits SHA-1 for "applications that do not require collision resistance". For anything where an adversary might supply the file, no — MD5 collisions are found in seconds, and a checksum an attacker can match is not a check. SHA-256 measured the same speed as MD5 on our own test machine, so there is no practical reason to keep either.

Is SHA-3 better than SHA-256?

Different, not better. SP 800-131A Rev. 2 lists both as "acceptable for all hash function applications", so the choice is about ecosystem support rather than strength. Our hash generator does not offer SHA-3, which is a gap rather than a judgement. Neither family belongs anywhere near a stored password.

Is bcrypt at cost 12 enough, and when should it change?

It is comfortably above OWASP's minimum work factor of 10 and costs 169.53 ms per hash on one core of the machine we measured. But NIST's wording is a standing instruction, not a one-off: the factor SHOULD be "increased over time to account for increases in computing performance". Treat it as a number with a review date. When a login feels instant on current hardware, add one.

Sources & verification