Blog

Migrating From Authy to Aegis

February 16, 2024

#howto

The smell of enshittification is in the air. Twilio announced that the desktop version of Authy is getting the axe on March 19, and this comes after a significant round of layoffs that Twilio performed at the end of 2023. For this reason it seemed like a ripe time to get my authenticator data out of Authy and into something more stable like Aegis. Based on a pick up in activity on GitHub it seems like I’m not alone in my thinking.

Authy itself does not have an official export feature, but all of the information needed to create a dump of account data from the desktop app is present in the comment thread of this GitHub gist.

There are a lot of comments in the gist thread, and it can be a chore to go through them all in order to piece together what to do. Consequently I’m documenting here what worked for me, and maybe it will help others get their data out too.

NOTE: This process was only tested with 6-digit TOTP accounts. Other kinds of accounts might not export correctly.

Tools Used

  • Arch Linux + AUR
  • Node.js
  • Chromium
  • KDE Connect (optional)

Install Authy From the AUR

Install the authy package from the AUR. With this method there isn’t any need to use an old version of the package. Version 2.5.0 was the latest as of the time of this writing.

Run Authy and Login

Start the app normally and login. Close the app once accounts show up.

Unpack Authy Electron Resources

This step is based on @fizzfaldt’s comment from the gist thread.

  1. Create a temp directory somewhere with normal user permissions. These examples will use /home/user/temp_authy.
$ mkdir /home/user/temp_authy
  1. The AUR package installs the contents of the Authy snap to /opt/authy. Use node to extract the inner resource archive to the temp directory.
$ npx @electron/asar /opt/authy/resources/app.asar /home/user/temp_authy

Run Authy With Remote Debugging Enabled

This step is based on @fizzfaldt’s comment from the gist thread.

Run the commands below to manually start the Authy app with a debugging port open. In older versions this used to be possible without taking the package apart.

$ cd /home/user/temp_authy
$ npx electron . --remote-debugging-port=5858 --remote-allow-origins=http://localhost:5858

After running the electron command the Authy app should open again.

Open the Debug URL in Chromium

  1. Open Chromium and go to http://localhost:5858. This should load a very plain looking white page.

  2. Click the “Twilio Authy” link. This should load a page with a debugging view of the app and dev tools all ready to go.

  3. For the next section it is important to the use the dev tools already present in the web page. These dev tools are setup with access to the Authy application state.

Dump Application Data Using the Dev Tools Console

This step is based on @brenc’s comment from the gist thread.

The code snippet in the thread by @brenc dumps information for all accounts in Raivo OTP format. The code shown below is my modified version that dumps account information in Aegis format instead.

  1. Copy/paste the code snippet shown below into the dev tools console and run it. This should cause a bunch of JSON output to print to the console. Note that the output contains all of the authenticator secrets and is sensitive!

  2. Copy/paste the generated JSON output into a text editor and save it to a file.

// Based on https://github.com/LinusU/base32-encode/blob/master/index.js
function hex_to_b32(hex) {
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  let bytes = [];
  for (let i = 0; i < hex.length; i += 2) {
    bytes.push(parseInt(hex.substr(i, 2), 16));
  }
  let bits = 0;
  let value = 0;
  let output = "";
  for (let i = 0; i < bytes.length; i++) {
    value = (value << 8) | bytes[i];
    bits += 8;
    while (bits >= 5) {
      output += alphabet[(value >>> (bits - 5)) & 31];
      bits -= 5;
    }
  }
  if (bits > 0) {
    output += alphabet[(value << (5 - bits)) & 31];
  }
  return output;
}

const items = appManager.getModel().map((i) => {
  let secretSeed = i.secretSeed;
  if (typeof secretSeed == "undefined") {
    secretSeed = i.encryptedSeed;
  }
  // @brenc: All of my Authy accounts have a 20 second period. Not sure why
  //         this was 10.
  const period = i.digits === 7 ? 20 : 30;
  const secret =
    i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(secretSeed);
  const [issuer, rawName] = i.name.includes(":")
    ? i.name.split(":")
    : ["", i.name];
  const name = [issuer, rawName].filter(Boolean).join(": ");

  return {
    type: "totp",
    // NOTE: Aegis generates a fresh UUID if we skip this property
    // uuid: null,
    name,
    issuer: name,
    icon: null,
    info: {
      secret,
      algo: "SHA1",
      digits: i.digits,
      period: period
    }
  };
});

// Example from https://github.com/beemdevelopment/Aegis/blob/master/app/src/test/resources/com/beemdevelopment/aegis/importers/aegis_plain.json
const aegis_data = {
  version: 1,
  header: {
    slots: null,
    params: null
  },
  db: {
    version: 1,
    entries: items
  }
};

// dumps entries to console in Aegis JSON format
console.log(JSON.stringify(aegis_data, undefined, 4));

Copy JSON File to Phone and Import Into Aegis

The JSON dump file is unencrypted, so use a secure method (like KDE Connect) to copy it to the phone. After it is copied over import the file into Aegis (Settings > Import & Export > Import from file). If using the code snippet provided in the previous section select “Aegis” as the import file format.

Testing

Since none of this process is official it’s important to test each imported account. Make sure that each account’s TOTP codes being generated in Aegis match the ones being generated in Authy.