Skip to content

BuildNot yet confirmed elsewhere1 publisher2 min readPublished

Django 6.1 cuts hardened password hashes down to its new 1.5M-iteration default at login

Django 6.1 raised its default PBKDF2 iteration count from 1.2M to 1.5M, adding about 50 to 90ms of CPU per login in a dev.to benchmark. The same tests show each stored hash rewritten on its owner's next login, including hashes deliberately set stronger than the new default.

The Engineer · Build desk

How we use AISend a correction

Illustration accompanying Django 6.1 cuts hardened password hashes down to its new 1.5M-iteration default at login
Generated illustration

What happened

  • The author timed encode() and verify() 20 times at each iteration count, across three runs on a 4-core container with nothing else running.
  • CPython's hashlib.pbkdf2_hmac, the call Django's PBKDF2 hasher makes, releases the GIL while OpenSSL computes the hash.
  • One authenticate() call on a user holding a 1.2M-iteration hash issued an UPDATE on auth_user that stored a 1.5M-iteration hash, with no migration or set_password() call.
  • A second login with the rewritten hash triggered no further write to the table.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Absorbing the extra 25% takes more CPU cores, because on the test box threads beyond the four cores added almost no hashing throughput.
  • cost Each returning user's first post-upgrade login is the most expensive one they will make, so login spikes right after a 6.1 deploy cost more CPU than steady state does.
  • exposure Users whose admins hardened hashes above the 1.5M default on purpose get weaker hashes back from a routine login.
  • decision Teams running a raised count have to choose before deploying 6.1 between keeping their own hasher live and accepting 1.5M iterations for every account that logs in.

The 50 to 90ms figure, from a benchmark write-up on dev.to, is single-threaded. It was measured on an otherwise idle 4-core container, and it varied with how busy the machine was [5]. The author read both defaults off PBKDF2PasswordHasher().iterations in 6.0.9 and 6.1.2 before timing anything [2]. The ratio is the portable part. Iterations rose by a factor of 1.25, and the fastest times moved by roughly that much in every run [4]. The milliseconds carry over to another fleet only if its cores compute PBKDF2-SHA256 at about that container's speed.

Concurrent logins are where a new choke point would show up, and the post found none [15]. On the same box, going from one thread to four raised hashing throughput by 3.5 to 4.0 times [16]. Eight threads added almost nothing over four [8]. "A login-heavy Django deployment pays for this in CPU headroom, not in a single blocked thread per request," the author wrote [9].

The write path is front-loaded. In the post's capture, the hash Django stored after that first login carried a new salt along with the new count [10]. So the request checked the password against the stored 1.2M-iteration hash, then ran a full encode at 1.5M. That is about 2.7M iterations in one login, or 2.25 times what a 6.0 login cost [17]. Each user pays it once [11]. After the deploy, the UPDATE count equals the number of distinct users who sign in, one row each [20]. The post does not measure database load from those writes.

Credit where due: upgrading hashes at login is good design. Every active user's hash gets stronger on their next sign-in, and nobody has to write a backfill job [14]. The problem is the comparison. Django asks whether the stored iteration count differs from the current default, not whether it is lower [12]. The author tested a hash set to 3,000,000 iterations, the kind a security-conscious admin might configure through a custom PASSWORD_HASHERS entry. One login left it at 1,500,000 [13]. Hashing time tracked the iteration count in the post's timings [4], so that login halved the cost of each offline guess against the hash [18].

The admins this catches are, by the post's own description, the security-conscious ones [13]. Under the same rule, a deployment whose live hasher still sets 3,000,000 iterations would find the stored and live counts equal and leave the hash alone [19]. The downgrade lands on sites where that custom hasher is no longer the live one when 6.1 arrives [19].

What to watch

  • Whether the Django project changes the rewrite check to fire only on a lower stored count, or documents the downgrade in the 6.1 release notes.
  • Login latency and auth_user write rates from production sites in the first days after a 6.1 deploy, set against the 4-core container figures.

Clarity's read

What the record supports and how the coverage leans. The claims behind it follow.

Reality

Evidence58
Adoption
Insufficient
Hype gap+20
Incentives
Insufficient
Confidence55
Why these scores

Claim ledger

Ranked by verification strength, evidence, and original report placement.

  1. [1]

    Django 6.1 shipped in August with a change to django.contrib.auth raising the default PBKDF2 iteration count for password hashing from 1,200,000 to 1,500,000.

    ReportedSupportedView cited source
  2. [2]

    Calling PBKDF2PasswordHasher().iterations returned 1200000 under Django 6.0.9 and 1500000 under Django 6.1.2, installed in separate virtualenvs.

    ReportedSupportedView cited source
  3. [3]

    The author hashed the same password 20 times at each iteration count, timing encode() and verify(), on a 4-core container with nothing else running, across three separate process runs.

    ReportedSupportedView cited source

Sources

1 independent publisher whose own reporting we read for this story.

  1. dev.to

    1 article · October 9, 2026

    Django 6.1's PBKDF2 default change rewrites existing password hashes on next login

Share your take

Let Clarity write the post for you.

Signed-in readers get a short post drafted on this story in the register they choose — narrative, analytical, or a direct position — editable to the last word before it goes anywhere. The share buttons at the top of this story work without an account.

Topics and entities

Follow any of these and your For You feed starts watching them — no settings page required.

Topics

Loading related stories