Password Hash Migration: The Technical Runbook
Updated 2026-09-12 · 14 min read · By @guptadeepak
Key takeaways
- Import support and export support are different products. Almost every platform documents how to bring hashes in. Far fewer document how to take them out.
- Lazy rehash on login is the default pattern: verify against the old algorithm, rehash with the new one inside the same successful login, retire the old column when the tail is small enough.
- Bcrypt is the lowest common denominator. If your hashes are in a format the destination cannot verify, a custom verification hook is the escape hatch.
- MFA enrollments usually do not migrate. TOTP secrets are exportable in principle and withheld in practice, and SMS enrollments are vendor-side state.
- Passkeys survive a migration only if the RP ID does not change and the destination will accept imported credential public keys.
- In B2B, every SAML connection and SCIM token has to be re-established by the customer's own IT admin, which makes migration a customer program rather than a cutover.
The asymmetry that decides everything
A password hash is not a secret you can regenerate. It is the only artifact that lets an existing user log in without being interrupted, and interruption is expensive: a forced reset on a consumer base reliably loses a percentage of dormant users permanently. That loss is the real cost of a migration, and it is entirely determined by whether hashes come with you.
Establish what you can actually get out
Before any planning, answer three questions about your current platform in writing. What hash algorithm and parameters are in use, in what encoding. Is there a documented export path for the hashes, an undocumented one through support, or none at all. And what does the export contain beyond the hash: salt, work factor, algorithm marker, and the user identifier that ties it to the rest of the record.
The answers sort your current platform into three tiers.
You own the database. Keycloak, FusionAuth, Supabase, Authentik, Ory, SuperTokens, and anything self-hosted. The hash is a column. Export is a query, and the only real work is understanding the stored format, which is often a PHC string that encodes the algorithm and parameters inline.
Hosted, with a documented export. A handful of hosted vendors will produce a hash export, sometimes only through a support request and under a specific contractual clause. Read the clause. "Data export" in a standard agreement frequently means profile data and excludes credential material.
Hosted, with no export. The hashes stay. Your options are a forced reset for everyone, or a proxy period where the old platform keeps verifying logins while the new one captures a fresh hash. The second option is described below, and it is how competent teams leave a platform that will not release credentials.
What the destination will accept
Destination platforms converge on a similar list, and bcrypt is the safe intersection. Auth0's bulk import accepts Argon2, bcrypt, PBKDF2, scrypt, HMAC, LDAP RFC-2307 userPassword, and the MD and SHA families, with format constraints that matter. Its bcrypt path expects $2a$ or $2b$ at ten rounds, and its Argon2 path expects a PHC string with no separate salt field. Cognito's CSV import accepts bcrypt, scrypt, Argon2id, and PBKDF2 with SHA-256.
Two consequences follow.
Exotic formats need a translation step, not a rejection. A legacy sha1(md5(password) + salt) construction is not importable anywhere as-is. It is still migratable: wrap it in a peppered outer hash, or carry it through a verification hook.
Round-tripping is lossy. Importing a bcrypt hash into a platform that stores Argon2id internally does not upgrade it. The credential remains bcrypt-strength until the user next logs in and it is rehashed. That is fine, and it is worth knowing when a security review asks why your stated algorithm and your actual stored algorithm differ for part of the base. The algorithm comparison in password security and storage covers what each format is actually worth.
The lazy rehash pattern
Rehash on login is the standard mechanism for moving a user base without interrupting anyone, and its correctness depends on one detail: the rehash happens inside the same request that already proved the password. The destination stores the legacy hash and an algorithm marker, verifies against them once, replaces them with its own hash, and never looks at the legacy value again.
The sequence:
on login(email, password):
user = load(email)
if user.legacy_hash is present:
if not verify_legacy(user.legacy_algo, user.legacy_hash, password):
return failure # do not fall through to the new hash
user.password_hash = hash_current(password)
user.legacy_hash = null
user.legacy_algo = null
save(user)
return success
return verify_current(user.password_hash, password)
Three rules make it safe.
Never verify against both and accept either. A user whose legacy hash verified must have the legacy column cleared in the same transaction. Leaving both populated creates two live credentials for one account, and the weaker one sets the security level.
Fail closed on an unknown legacy algorithm. A record whose algorithm marker is missing or unrecognized is a data error, not a login to allow through.
Count the migration. Emit an event on every rehash and track the proportion of the base still carrying a legacy hash. Without that counter you cannot decide when the forced-reset step is safe, and instrumentation of exactly this kind is the subject of the wider observability discipline these migrations expose.
For a destination that will not run your code at login, the same pattern exists as a vendor primitive. Okta's password import inline hook fires on the user's first sign-in and calls your service to verify the credential against the old store, after which Okta takes ownership of the password. Auth0's custom database with import mode does the same thing. Both are how you leave a platform that refuses to export.
What cannot migrate
Passwords are the part of a migration that can be made invisible. Everything else in the credential inventory usually cannot. Second factors, recovery codes, and passkeys are either sealed by the source platform, bound to an identifier that is about to change, or both. Set expectations on this early, because it is the part that generates support volume and the part a cutover plan tends to discover late.
SMS and voice MFA enrollments. These are vendor-side records tied to a delivery integration. They are re-enrolled at the destination. Given that SMS is on its way out anyway, a migration is a reasonable moment to move that population to a stronger factor rather than reproducing the old one.
TOTP shared secrets. Portable in principle, withheld in practice. Most hosted platforms treat the shared secret as sealed material that never leaves. Assume re-enrollment, communicate it ahead of the cutover, and keep a grace period where a user can re-enroll without a support ticket.
Recovery codes. Regenerate them. They are hashed at rest in any competent implementation, which means they are subject to the same export problem as passwords with none of the benefit of carrying them over.
Passkeys, unless two conditions hold. This is the one most migration plans get wrong. A passkey is scoped to the relying party identifier it was created against. If your login lives on accounts.example.com today and the new vendor hosts it on example.vendor.com, every existing passkey is invalid regardless of what either platform exports. Where the RP ID is preserved, the credential record itself is portable in principle: credential ID, public key, signature counter, transports, and AAGUID are ordinary data. Whether the destination will accept an import of those fields is a direct question to ask, and many will not.
Sessions. Everyone is logged out at cutover. Say so in the comms.
The B2B coordination problem
In B2C, migration is something you do to your own database on your own schedule. In B2B, the credential is often not the hard part at all, because federated users have no password with you. The hard part is that every enterprise customer's identity configuration is owned by that customer's IT administrator.
For each enterprise tenant, someone on the customer side has to update the SAML or OIDC connection to point at new endpoints and a new entity ID, re-establish trust with a new certificate, re-issue a SCIM bearer token against a new base URL, re-map attributes if the destination names them differently, and re-test provisioning and deprovisioning. See SCIM provisioning and enterprise SSO for what each of those steps involves.
None of that happens on your timeline. It happens on their change calendar, which for a regulated customer may be a quarterly window. Plan accordingly:
Do
Sequence tenants by risk and size
Start with the smallest and friendliest customers, and keep both platforms live through the overlap.
The first three tenants surface every mapping bug the sandbox missed.
Give each admin a filled-in runbook
Send the customer's own entity ID, endpoints, and token, not a link to generic documentation.
Generic docs produce support tickets; pre-filled values produce completed migrations.
Dual-accept provisioning during the overlap
A tenant that has not cut over yet must keep provisioning against the old endpoint without special handling.
Enterprise change calendars run on quarters, not on your release schedule.
Treat a stalled tenant as a project
Assign an owner and a date, because a connection nobody owns never gets re-established.
The long tail of a B2B migration is measured in months, not weeks.
Don't
Announce one global cutover date
Federated tenants cut over individually, on their own administrators' schedules.
A single date turns every late customer into an outage you caused.
Assume the customer remembers the original configuration
The admin who set up the SAML connection has usually moved on, and the settings are undocumented on their side.
Provide current values and expected values side by side.
Deprovision the old connection early
Keep it until you have seen a successful login and a successful SCIM sync on the new one.
Rollback is only possible while the old path still works.
Let the overlap run open-ended
Two live identity systems means twice the attack surface and twice the audit scope.
Set the end date when the overlap begins.
Pre-migration checklist
Work through this before writing any code. Each item has sunk a migration that skipped it.
- Get the export answer in writing. Algorithm, parameters, encoding, delivery mechanism, and the contract clause that entitles you to it.
- Verify one hash end to end. Take a single exported hash, import it into a sandbox on the destination, and log in as that user. Do this in week one, not week six.
- Inventory the credential types you hold. Passwords, TOTP, SMS, passkeys, recovery codes, social identities, and federated connections. Each has a different migration path and some have none.
- Confirm the RP ID. If it changes, passkeys are gone and your passwordless rollout restarts.
- Map social and federated identities by provider subject. A Google-linked account keys on the
subclaim, and a destination that keys on email address will silently merge or split accounts. - Decide the forced-reset date up front. Announce the lazy window with an end, or it will never end.
- Plan the rollback. Keep the source system readable, not just running, until the counter of unmigrated accounts is small enough to accept.
- Rehearse on a copy. A full-volume dry run against a sandbox is the only way to discover the encoding problem that affects 4 percent of your rows.
- Write the user communication before the cutover, not during. The re-enrollment message is the one users will actually read.
- Instrument the tail. Migrated versus unmigrated counts, rehash events per day, failed legacy verifications, and support contacts tagged to the migration.
The broader sequencing, vendor selection, and rollback strategy sit in the CIAM migration framework. This runbook is the credential layer underneath it, and it is the layer that decides whether your users notice.
Related vendors
Auth0
Auth0 remains the safest mid-market default for B2C plus B2B Enterprise SSO when developer velocity matters more than long-run TCO. Auth0 for AI Agents (GA November 2025) and Auth for MCP (GA May 2026) make it the first major CIAM with a packaged agent-identity surface. Below 50k MAU it is still hard to beat. Above 500k MAU, cost and Actions-driven lock-in make FusionAuth, Cognito, or Stytch (Twilio) plus a passkey orchestrator the more honest shortlist.
Amazon Cognito
Amazon Cognito is the right CIAM choice when the application is already deep in AWS and the buyer values IAM integration plus FedRAMP / PCI / HIPAA over developer velocity. Native WebAuthn passkeys now ship in Managed Login; orchestration quality is still thin compared with Stytch or Descope. Per-MAU economics beat SaaS competitors above 500k MAU. Outside AWS-native architectures, the DX gap relative to Auth0 / Clerk / Stytch is hard to justify.
FusionAuth
FusionAuth is the right answer when you want self-hosted CIAM without taking on Keycloak's operational weight, and want the option to switch to managed without changing vendors. Single-binary deploy, modern docs, and a genuinely usable Community tier make it the practical default for self-host evaluations in 2026, particularly for B2C and mid-market B2B SaaS that don't need FedRAMP or Zanzibar-style FGA.
Stytch
Stytch is the strongest passkey-first CIAM in 2026 by orchestration quality, not raw feature count. Twilio acquired it on October 30, 2025; the product runs as a Twilio subsidiary with its own API surface, SDK family, and pricing, distinct from Twilio Verify. Post-acquisition the platform combines Stytch's modern auth with Twilio's communications infrastructure, repositioning it as a credible Auth0 alternative for developer-focused teams. Below 500k MAU the case is strong for both B2C and B2B SaaS; beyond that, gaps on FedRAMP, FGA, and adaptive MFA depth narrow it.
WorkOS
WorkOS is the strongest B2B-first CIAM in 2026 by deliberate scope choice: every product surface assumes the buyer is selling to enterprise IT, not to consumers. AuthKit's 1M MAU free tier makes it a credible Auth0 alternative for B2B SaaS that does not need adaptive risk or B2C consumer flows. In 2026 the company is also documenting MCP step-up patterns for agents; that is still a tutorial surface, not a packaged agent-identity product like Auth0 for AI Agents. For pure B2B SSO, SCIM, and audit logs, WorkOS is hard to beat at any price point.
Where to next
FAQ
- Can you export password hashes from a CIAM provider?
- Sometimes, and it depends entirely on the provider rather than on the format. Self-hosted platforms such as Keycloak, FusionAuth, and Supabase keep hashes in a database you control, so export is a query. Hosted platforms vary: some will produce a hash export on request or through a support process, and others never release hashes at all. Amazon Cognito is the clearest example of the asymmetry, because it added password-hash import in July 2026 while still offering no path to export the hashes it holds. Confirm the export path in writing before signing, not at migration time.
- What is lazy migration or rehash on login?
- A pattern where users are moved without ever being asked to reset a password. The destination system stores the legacy hash alongside a marker for its algorithm. On the user's first login, it verifies the submitted password against the legacy hash, and if that succeeds, it immediately computes a new hash with its own algorithm, writes that, and clears the legacy value. The user notices nothing. After a defined window the remaining unmigrated accounts are handled by a forced reset, because the tail of a login-driven migration never reaches zero.
- Can you migrate passkeys between CIAM vendors?
- Only under two conditions. The relying party identifier has to stay the same, because a passkey is cryptographically scoped to the RP ID it was created against, and a move from your own domain to a vendor-hosted login domain invalidates every credential. The destination also has to accept imported WebAuthn credential records, which means credential ID, public key, sign count, and transports. The FIDO Credential Exchange specifications address portability between credential managers, which is a different problem from relying-party migration.
- Do MFA enrollments survive a CIAM migration?
- Usually not. SMS and voice enrollments are vendor-side state tied to a delivery integration, so they are re-enrolled at the destination. TOTP shared secrets are technically portable and rarely exported, because most hosted platforms treat them as sealed material. Plan for a re-enrollment campaign with a grace period, and expect it to be the most visible part of the migration to end users.
- How long should you run dual-algorithm verification?
- Long enough to catch your seasonal users, which for most consumer products means one to two login cycles of your least active cohort. Ninety days captures the bulk of an active base. Anything beyond about six months is carrying two verification paths for a shrinking population, and at that point a forced reset on the remainder is cheaper and safer than keeping a legacy algorithm alive in production.
- What makes B2B migrations harder than B2C?
- The coordination surface. A consumer migration is something you do to your own database. A B2B migration requires every enterprise customer's IT administrator to re-establish a SAML or OIDC connection, re-issue a SCIM bearer token, and re-test provisioning against the new endpoint, on their change calendar rather than yours. The technical work is smaller than the consumer case and the elapsed time is longer.
Sources
- Auth0 Docs, User Data JSON Schema for Bulk User Imports (supported hash algorithms)
- AWS, Amazon Cognito now supports importing users with password hashes (July 2026)
- Okta Developer, Password import inline hook
- OWASP Password Storage Cheat Sheet
- FIDO Alliance, Credential Exchange Specifications (CXF / CXP)