Keyhop: Migrate off Google Authenticator
Many years ago I adopted Google Authenticator for one time passwords, and it has quietly followed me from phone to phone ever since. Each move works the same way: the old phone displays an export QR code, the new phone scans it, and the accounts hop over. That works fine until you want to leave. I’ve wanted to switch to an app with a better backup story for a while, but I didn’t want to log into every service and re-enroll two factor auth one account at a time. The export QR code clearly contains everything a new phone needs, so I figured there had to be a way to extract that information myself.
Keyhop is the result. It’s a small Gleam and Lustre app, running at keyhop.jtdowney.com, that scans a Google Authenticator export with your webcam and re-emits each account as a standard QR code that any other authenticator app can enroll.
What’s actually in the export
When you use “Transfer accounts” in Google Authenticator, the QR codes it displays encode an otpauth-migration://offline?data=... URI. The data parameter is a base64 encoded protobuf message containing each account’s secret, issuer, label, algorithm, digit count, and whether it’s TOTP or HOTP. The schema is well understood at this point, and large exports split across multiple QR codes with batch fields so you can tell when you’ve scanned the whole set.
How it works
Keyhop takes advantage of the relatively straightforward webcam API that browsers have now. The flow has three steps:
- Scan. Point your webcam at the export QR codes. Multi-code exports are stitched together, and keyhop tracks which codes from the batch you still need.
- Validate. Each extracted account shows a live code, so you can compare it against what Google Authenticator displays and confirm the extraction actually worked before you rely on it.
- Hop. A slideshow walks through one standard QR code per account, and you scan each with your new app.
One wrinkle: Google Authenticator shows the export on the phone that holds your accounts, and a phone can’t scan its own screen. Run keyhop on a second device, like a laptop with a webcam, and hold the phone up to it.
Trusting a website with your secrets
TOTP secrets are exactly the kind of thing you shouldn’t paste into a random website, so keyhop is built to need no trust in a server. It’s a static page with no backend, everything runs in your browser, and the decoded secrets never leave your device. If that still isn’t comforting enough, the source is on GitHub and you can clone it and run pnpm dev locally.
Under the hood
The app is written in Gleam, compiled to JavaScript, with Lustre handling the UI. Camera frames go from getUserMedia onto a canvas, and zxing-wasm finds the QR codes in them. The protobuf payload is decoded with code generated by protoc-gen-gleam, and glqr renders the outgoing QR codes.
It is very niche, but it fit a need I had, and I’m sharing it because why not.