<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Gleam on John Downey</title><link>https://jtdowney.com/tags/gleam/</link><description>John Downey (Gleam)</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sun, 23 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jtdowney.com/tags/gleam/index.xml" rel="self" type="application/rss+xml"/><item><title>Keyhop: Migrate off Google Authenticator</title><link>https://jtdowney.com/blog/2026/08/23/keyhop-migrate-off-google-authenticator/</link><pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate><guid>https://jtdowney.com/blog/2026/08/23/keyhop-migrate-off-google-authenticator/</guid><description>&lt;p&gt;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&amp;rsquo;ve wanted to switch to an app with a better backup story for a while, but I didn&amp;rsquo;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.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/jtdowney/keyhop"&gt;Keyhop&lt;/a&gt; is the result. It&amp;rsquo;s a small &lt;a href="https://gleam.run/"&gt;Gleam&lt;/a&gt; and &lt;a href="https://lustre.build/"&gt;Lustre&lt;/a&gt; app, running at &lt;a href="https://keyhop.jtdowney.com/"&gt;keyhop.jtdowney.com&lt;/a&gt;, 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.&lt;/p&gt;
&lt;h2 id="whats-actually-in-the-export"&gt;What&amp;rsquo;s actually in the export&lt;/h2&gt;
&lt;p&gt;When you use &amp;ldquo;Transfer accounts&amp;rdquo; in Google Authenticator, the QR codes it displays encode an &lt;code&gt;otpauth-migration://offline?data=...&lt;/code&gt; URI. The &lt;code&gt;data&lt;/code&gt; parameter is a base64 encoded protobuf message containing each account&amp;rsquo;s secret, issuer, label, algorithm, digit count, and whether it&amp;rsquo;s &lt;a href="https://datatracker.ietf.org/doc/html/rfc6238"&gt;TOTP&lt;/a&gt; or &lt;a href="https://datatracker.ietf.org/doc/html/rfc4226"&gt;HOTP&lt;/a&gt;. 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&amp;rsquo;ve scanned the whole set.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;Keyhop takes advantage of the relatively straightforward webcam API that browsers have now. The flow has three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Scan.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validate.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hop.&lt;/strong&gt; A slideshow walks through one standard QR code per account, and you scan each with your new app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One wrinkle: Google Authenticator shows the export on the phone that holds your accounts, and a phone can&amp;rsquo;t scan its own screen. Run keyhop on a second device, like a laptop with a webcam, and hold the phone up to it.&lt;/p&gt;
&lt;h2 id="trusting-a-website-with-your-secrets"&gt;Trusting a website with your secrets&lt;/h2&gt;
&lt;p&gt;TOTP secrets are exactly the kind of thing you shouldn&amp;rsquo;t paste into a random website, so keyhop is built to need no trust in a server. It&amp;rsquo;s a static page with no backend, everything runs in your browser, and the decoded secrets never leave your device. If that still isn&amp;rsquo;t comforting enough, the source is on &lt;a href="https://github.com/jtdowney/keyhop"&gt;GitHub&lt;/a&gt; and you can clone it and run &lt;code&gt;pnpm dev&lt;/code&gt; locally.&lt;/p&gt;
&lt;h2 id="under-the-hood"&gt;Under the hood&lt;/h2&gt;
&lt;p&gt;The app is written in Gleam, compiled to JavaScript, with Lustre handling the UI. Camera frames go from &lt;code&gt;getUserMedia&lt;/code&gt; onto a canvas, and &lt;a href="https://github.com/Sec-ant/zxing-wasm"&gt;zxing-wasm&lt;/a&gt; finds the QR codes in them. The protobuf payload is decoded with code generated by &lt;a href="https://github.com/qwexvf/protoc-gen-gleam"&gt;protoc-gen-gleam&lt;/a&gt;, and &lt;a href="https://hexdocs.pm/glqr/"&gt;glqr&lt;/a&gt; renders the outgoing QR codes.&lt;/p&gt;
&lt;p&gt;It is very niche, but it fit a need I had, and I&amp;rsquo;m sharing it because why not.&lt;/p&gt;</description></item></channel></rss>