BlogMigration

What happens to your 2FA codes when you switch vaults

6 min read

If you have ever moved between password managers, you have probably clicked Export, got a CSV, and not looked too closely at it. It is worth looking. Alongside every password, most exports carry the two-factor secret for that account — the seed your authenticator uses to generate six-digit codes — in plain text, in the same row.

That file is the most dangerous document you will ever have on your desktop, and the thing that happens to it next is usually: you upload it to a new vault, which stores the seed in the same record as the password, and you delete the CSV and forget about it. This post is about the step in the middle, and what our importer does differently.

The seed is not the code

A six-digit code is derived, every thirty seconds, from two things: the current time, and a secret shared between you and the site. That secret — the seed — is the durable half. The codes are disposable; the seed is the account.

This matters for migration because a code is useless to an attacker twenty seconds later, but a seed is useful forever. When an export writes `otpauth://totp/...?secret=JBSWY3DPEHPK3PXP` into a CSV cell, it has written something equivalent in value to the password sitting next to it — and it has written both into the same row of the same unencrypted file.

What most importers do with it

They keep it. The seed is parsed out of the export and written into the new vault, in the same record as the password, and from then on the vault generates your codes for you. It autofills the password, waits a beat, and autofills the code. It is genuinely a good experience, and it is why the feature exists.

It also means that from the moment the import finishes, one compromised vault yields both factors for every account you migrated. Not the password and a second obstacle — the password and the thing that generates the second obstacle. We have written the long version of that argument separately; this post assumes it rather than repeating it.

What ours does instead

The importer reads the seeds. It has to: it cannot tell you what it found, or hand them onward, without parsing them. What it does not do is put them in the vault.

Each seed found on a login is turned into a complete `otpauth://` URI and held in memory for the length of the import. At the end, those become QR codes on screen for you to scan into an authenticator app — ours, or Aegis, or whatever you already use. They are never encrypted into an item, and they are never uploaded. The type that carries them through the importer says so in its own definition, which is the only kind of documentation that cannot drift from the code:

  • “A seed found on a login, destined for the Authenticator — never the vault.”
  • “A complete otpauth:// URI. Held in memory only; never uploaded.”

Why it refuses a seed it cannot decode exactly

Seeds are Base32. The decoder in the importer returns nothing rather than a best-effort partial decode, and it rejects character counts that are structurally impossible for whole bytes before it even starts.

That strictness is deliberate, and the reason is about when you would find out. A seed that decodes to almost-the-right bytes produces codes that look perfectly normal and are always wrong. You would not discover that at import time, with the old vault still open in another tab. You would discover it weeks later, at a login prompt, locked out of an account whose recovery codes are in the vault you just migrated away from.

One limit worth stating plainly: Base32 carries no checksum, so a seed that was truncated to a still-valid length decodes cleanly to the wrong bytes and nothing in the importer can detect it. Scan the QR codes and confirm one code against the site before you delete your old vault.

What this costs you

An extra step, and a second app. The import does not finish with everything tidied into one place; it finishes with a screen of QR codes and a job still to do. If you migrate forty logins with two-factor on a dozen of them, that is a dozen scans.

It also means autofill will fill your password and then stop. Your phone has the code, not your browser. For most people that is a few seconds per login; if you sign in to the same handful of sites all day, it is a few seconds you will pay repeatedly, and you should weigh that honestly rather than take our word that it does not matter.

When keeping both in one vault is the right call

If the realistic alternative is that you give up on two-factor altogether, put the seeds in your vault. A second factor stored beside the first is still better than no second factor, by a wide margin, and anyone who tells you otherwise is arguing about the wrong threat.

The same goes if your threat model is a phished password rather than a breached vault — which, for most people, is the likelier event. A vault-stored TOTP still defeats a credential stuffed from somebody else’s breach. Our position is that the split is worth it anyway, because it is cheap to keep and expensive to retrofit after the fact. It is a judgement, not a proof, and you are allowed to reach a different one.

Common questions

Will my passwords import if I do not want the two-factor part?
Yes. The passwords, usernames, URLs, folders and notes all import normally. The seeds are the only thing handled separately, and if you ignore the QR codes entirely you simply keep using whatever authenticator you already have.
Which exports does the importer read?
LastPass, Bitwarden, 1Password and Chrome. Each has its own parser, because each writes two-factor secrets into a different column in a different format.
Do the QR codes go anywhere?
No. They are rendered in your browser from data that stays in memory for the length of the import, and they are gone when you close the page. If you miss one, re-run the import rather than looking for it somewhere on a server.
Is the authenticator a paid product?
The codes are free and unlimited. Its paid tier covers encrypted backup and moving to a new phone — not the codes themselves.

Move your passwords without moving your second factor

Import from LastPass, Bitwarden, 1Password or Chrome, and take your two-factor seeds out as QR codes on the way through.