Keyhop: Migrate off Google Authenticator

I’ve been using Google Authenticator to generate one time passwords for many years. When I get a new phone, it’s pretty simple to get all of my accounts transferred over using the export QR code on my old phone and then scanning the QR code with my new phone. I’ve wanted to switch to an authenticator app that has a better backup story for some time, but that would mean having to log into each service and re-enroll in two factor auth, account by account.

The export QR code from Google Authenticator clearly contains everything a new phone needs to be able to generate the same codes, so I set out to write a program to help me extract that information myself. Keyhop is a small Gleam and Lustre app available at keyhop.jtdowney.com. The app uses your webcam to scan an export from Google Authenticator, and then re-issues each account as a standard QR code that any other authenticator app can easily enroll.

What’s actually in the export

If you’re not familiar with the “Transfer accounts” feature in Google Authenticator, it displays QR codes that encode a URI of the form otpauth-migration://offline?data=.... The data parameter is a base64 encoded protobuf message. This contains each account’s secret and a few metadata fields: issuer, label, algorithm, digit count, and whether it’s TOTP or HOTP. The schema is well understood at this point, and large exports that are split over multiple QR codes include batch fields to keep track of which codes go together.

How it works

Keyhop takes advantage of how easy it is to use your webcam from the browser nowadays. There are three steps:

  1. Scan. Point your webcam at your export QR codes. If you have a multi-code export, they’ll get stitched together and keyhop will keep track of which codes from the batch you still need.
  2. Validate. For each extracted account a live code will be displayed. You can compare this with what Google Authenticator shows you, and be sure the extraction actually worked.
  3. Hop. A slideshow will guide you through one standard QR code for each account, while you scan them with your new app.

But wait, Google Authenticator shows the export on the phone that holds your accounts, and a phone can’t scan its own screen. The answer is to run keyhop on a second device, say a laptop with a webcam, and hold your phone up to it.

Trusting a website with your secrets

You definitely should not be pasting your TOTP secrets into a random website! This is exactly what keyhop is built to avoid. It’s a static page with no backend, which means you don’t need to trust a server. It does everything right inside your browser and the decoded secrets never leave your device.

If that still isn’t comforting enough, the code is available on GitHub, so you can clone it and run it yourself with pnpm dev.

Under the hood

It’s mostly written in Gleam (compiled to JavaScript), with Lustre doing the UI. It gets the camera frames using getUserMedia and puts them on a canvas, which zxing-wasm then scans for QR codes. The contents of the QR code is a protobuf payload, which is decoded using code generated by protoc-gen-gleam. Then glqr encodes each account back into an outgoing QR code.

Yes, this is very niche. But it fit a need I had. And why not share it with the world?