A 2FA setup QR code contains a single line of text, an otpauth://totp/... URI. The Base32 string after secret= is the key itself. With that one line, a standard authenticator app can rebuild exactly the same codes, so a screenshot of the setup QR is enough to restore your 2FA on a new device, as long as the service hasn’t replaced the secret since.
A QR code exported from Google Authenticator’s Transfer codes screen is a different thing. It is made for the app’s own import step and is covered separately below.
This guide breaks the URI down field by field, then walks through the recovery with two in-browser tools: read the screenshot, check the secret against your old codes, and enroll a new app. Every example uses the public test secret JBSWY3DPEHPK3PXP from the specification, never a real key.
The otpauth URI, Field by Field
The format is defined on the Key Uri Format page of the Google Authenticator project:
otpauth://TYPE/LABEL?PARAMETERS
A typical setup URI looks like this:
otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
| Field | Required / default | What to watch for |
|---|---|---|
| type | Required | totp here. hotp is the counter-based variant and needs a counter parameter instead of a period |
| label | Required | For example Example:alice@example.com: the account name, optionally prefixed by the issuer and a colon (literal or %3A). Neither part may contain a colon |
| secret | Required | Base32: letters A–Z and digits 2–7. The spec says = padding should be omitted |
| issuer | Strongly recommended. If absent, may be taken from the label prefix | If both are present, they should be equal |
| algorithm | Optional, default SHA1 | SHA256 and SHA512 are the other allowed values |
| digits | Optional, default 6 | 6 or 8 |
| period | Optional, default 30 | Seconds per code |
Two details cause most of the confusion when you read a real URI:
- Percent-encoding. The label and issuer are URL-encoded, so
%20is a space,%40is@and%3Ais a colon.ACME%20Co:john.doe@email.comis the spec’s own example. - Missing parameters mean defaults. A URI with only
secretandissueris an ordinary SHA-1, 6-digit, 30-second TOTP. When you copy the settings by hand, write those defaults down too.
The spec page was last edited in 2018. Its notes from that time say Google Authenticator ignored algorithm and period, and ignored digits on Android. It doesn’t document how today’s apps behave, so if your URI uses non-default values, check the codes after you enroll the new app.
For how the secret, the clock and HMAC turn into a 6-digit code, see how TOTP works.
Step 1: Read the URI from a Screenshot
Open the QR code decoder, then choose, drop or paste the screenshot. The image is decoded in your browser and is not uploaded.
The decoder shows the payload as Text and prints the full original payload. It doesn’t recognize otpauth:// as a special type, so it won’t split out the secret, issuer or algorithm for you. Read them from the URI using the table above:
otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
^^^^^^^^^^^^^^^^ secret
If the decoded text doesn’t start with otpauth://totp/, stop here: the rest of this guide doesn’t apply to it.
Step 2: Check the Secret Against Your Old Codes
Before you rely on the secret, prove it is the right one. If the old device still works, compare codes side by side.
- Open the TOTP generator and stay on the Generate tab.
- Paste only the Base32 value after
secret=into Base32 secret. Pasting the whole URI won’t work: the field accepts Base32 only and reports an invalid secret. - If the URI sets
algorithm,digitsorperiod, open Advanced options and match them. Otherwise leave the defaults. - Compare the Current code with the code on the old device. They should be identical within the same time window.
If they differ, check the computer’s clock first, then each parameter. A single mistyped character changes every code.
If you’d rather check in a terminal, the Python library pyotp reads the whole URI:
import pyotp
uri = "otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
print(pyotp.parse_uri(uri).now())
Step 3: Restore the Account on a New Device
- In the TOTP generator, keep the secret and any advanced options from step 2, then switch to the Set up tab.
- Fill in Issuer and Account with the values from the label, so the new app shows a recognizable name.
- Scan the QR code on the screen with the new authenticator app. Check that the app shows the same code as the Generate tab.
- Sign in to the service with a code from the new app to confirm it works.
The Set up tab also shows the rebuilt otpauth:// URI, which some password managers accept as a pasted value. Our QR code generator can encode any text as a QR image, but a QR of this URI is as sensitive as the original screenshot.
Rotate the secret if the screenshot might have been exposed. Set up 2FA again in the service’s security settings so the account gets a new secret, then confirm that codes from the old secret are rejected. After that, the old screenshot is useless to anyone who finds it.
Google Authenticator Export QR Codes: Why This Guide Can’t Use Them
Google Authenticator has its own transfer path: on the old phone, Transfer codes → Export codes creates a QR code, and on the new phone, Transfer codes → Import codes scans it. When you export more than one account, the old phone may create more than one QR code (Google’s help page).
Google documents this QR code only as something to scan with Import codes. It doesn’t publish what the code contains, and the steps in this guide assume the standard otpauth://totp/ setup URI described above. If you decode an export QR and the text doesn’t start with otpauth://totp/, our TOTP generator can’t use it. Import it into Google Authenticator on the new phone as Google describes.
The same help page says codes saved to your Google Account sync automatically when you sign in to Google Authenticator on a new device. In that case you may not need a QR code at all.
Keep the Secret Secret
- The secret is the second factor. TOTP codes are computed from the secret and the current time, so anyone with the URI or a screenshot of the QR can generate valid codes for as long as the secret stays active.
- Store setup screenshots like passwords. An encrypted password manager or an encrypted drive is a reasonable place. A camera roll that syncs to a photo cloud is not. Delete copies you don’t need.
- Never paste a URI into a search box or a chat. A search engine receives whatever you type into its box, and a chat message lives in someone else’s logs. If a secret has ended up in either place, rotate it as described in step 3.
FAQ
Does an old 2FA QR screenshot still work?
Yes, as long as the service still uses that secret. The QR code holds fixed text and has no expiry of its own. Once you reset or re-enroll 2FA on the service, the old secret stops working and so does the screenshot.
My secret has lowercase letters or spaces. Is it valid?
Usually, yes. Base32 is designed to be case-insensitive (RFC 4648), and spaces are often added only to make the key easier to read. Our TOTP generator strips spaces and uppercases the secret. Letters outside A–Z and digits other than 2–7 are invalid.
Why does the URI contain %40 or %20?
The label and issuer are URL-encoded. %40 is @, %20 is a space and %3A is a colon. Decode them before typing the account name into a new app. A spec-compliant secret is plain Base32 without padding, so it contains no percent signs.
Can someone work out the secret from my 6-digit codes?
Not in practice. RFC 4226’s security analysis concludes that an attacker who collects many valid codes gains no significant advantage over random guessing, and the best attack is brute force. The secret is what has to be protected, not the codes.
References
- Key Uri Format, Google Authenticator project wiki
- RFC 6238: TOTP
- RFC 4226: HOTP
- RFC 4648: Base16, Base32, and Base64 Data Encodings
- Get verification codes with Google Authenticator, Google Account Help