Skip to content

Instantly share code, notes, and snippets.

@jfdesrochers
Forked from mapster/Readme.md
Created March 22, 2023 19:25
Show Gist options
  • Save jfdesrochers/5b5866af24fc992fbed43655e91da07a to your computer and use it in GitHub Desktop.
Save jfdesrochers/5b5866af24fc992fbed43655e91da07a to your computer and use it in GitHub Desktop.
Export Google Authenticator secret OTP-keys

Export Google Authenticator secret OTP-keys

I recently got myself a Yubikey and wanted to set up the Yubico Authenticator with all the OTPs I had in Google Authenticator. Unfortunately Yubico Authenticator doesn't support scanning the QR-code that the Google Authenticator generates when you export the OTP-keys, and it seemed like quite the daunting task to log in to every service to generate new OTP-keys. So I decided to have a look at the contents of the QR code, to see if I could import the keys into Yubico Authenticator in one go. Luckily I found a blog post by Alex Bakker that describes the data format.

Transfer QR-code to computer

Unfortunately, but likely for the best, the security policy of Google Authenticator won't allow you to take a screenshot of the generated export-all QR-code. Since my phone is also the only device I own with a decent camera, I had to resign to snap a picture of QR-code on the phone screen using the built-in webcam of my laptop. If you also use a low quality camera you might run into the same issue that I did, namely that the picture will have too much noice for QR-code readers to interpret the QR-code. The easiest way around it was split the export into multiple QR-codes, which for me meant two codes instead of twenty. I used the Linux desktop app Kamoso to snap the pictures.

Extract OTP-keys

To extract the OTP-keys from the Google Authenticator QR-code is a four-step procedure:

  1. Extract data-URL from the QR-code
  2. Base64 Decode the query parameter data
  3. Decode the protobuf message
  4. For each OTP-key; base32 decode the secret field

Requirements

  • nodejs
  • zbar-tools

The zbar-tools package includes a tool to extract URLs from QR-codes. I did try to use jimp and qrcode-reader in the javascript, but it didn't work straight out the box so I didn't bother spending more time to get it to work.

Usage

  1. Download the files package.json, index.js, migration-payload.proto and otp-codes.sh to an empty directory
  2. Make otp-codes.sh executable: chmod +x otp-codes.sh
  3. Extract codes ./otp-codes.sh <path to qr-code image>
const protobuf = require("protobufjs");
const fs = require('fs');
const base32 = require('hi-base32');
async function decodeMessage(buffer) {
const root = await protobuf.load("migration-payload.proto");
const payload = root.lookupType("MigrationPayload");
const err = payload.verify(buffer);
if (err) {
throw err;
}
const message = payload.decode(buffer);
const obj = payload.toObject(message);
return payload.toObject(message);
}
async function printOTPCodes(otpBuffer) {
const payload = await decodeMessage(otpBuffer);
const otpArray = payload.otpParameters;
for(let i = 0; i < otpArray.length; i++) {
const otp = otpArray[i];
console.log("Issuer: " + otp.issuer);
console.log("Name: " + otp.name);
console.log("Secret: " + base32.encode(otp.secret));
console.log("-----------------------------------");
}
}
const url = new URL(process.argv[2]);
const otpBuffer = Buffer.from(url.searchParams.get('data'), 'base64');
printOTPCodes(otpBuffer).catch(err => console.error(err));
syntax = "proto3";
message MigrationPayload {
enum Algorithm {
ALGO_INVALID = 0;
ALGO_SHA1 = 1;
}
enum OtpType {
OTP_INVALID = 0;
OTP_HOTP = 1;
OTP_TOTP = 2;
}
message OtpParameters {
bytes secret = 1;
string name = 2;
string issuer = 3;
Algorithm algorithm = 4;
int32 digits = 5;
OtpType type = 6;
int64 counter = 7;
}
repeated OtpParameters otp_parameters = 1;
int32 version = 2;
int32 batch_size = 3;
int32 batch_index = 4;
int32 batch_id = 5;
}
#!/bin/bash
qrcode="$(zbarimg $1 2>/dev/null)"
url="${qrcode/#QR-Code:}"
echo "Parsing: $url"
echo "-----------------------"
echo ""
node index.js "$url"
{
"name": "google-authenticator-otp-key-extractor",
"version": "1.0.0",
"description": "",
"main": "index.js",
"author": "Alexander Hoem Rosbach <[email protected]>",
"license": "MIT",
"dependencies": {
"hi-base32": "^0.5.0",
"protobufjs": "^6.10.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment