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.