Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save JacobJohansen/5f688d45049be440b8ee87cc5e854b75 to your computer and use it in GitHub Desktop.
Save JacobJohansen/5f688d45049be440b8ee87cc5e854b75 to your computer and use it in GitHub Desktop.
Export TOTP tokens from Authy

Generating Authy passwords on other authenticators


There is an increasing count of applications which use Authy for two-factor authentication. However many users who aren't using Authy, have their own authenticator setup up already and do not wish to use two applications for generating passwords.

Since I use 1Password for all of my password storing/generating needs, I was looking for a solution to use Authy passwords on that. I couldn't find any completely working solutions, however I stumbled upon a gist by Brian Hartvigsen. His post had a neat code with it to generate QR codes (beware, through Google) for you to use on your favorite authenticator.

His method is to extract the secret keys using Authy's Google Chrome app via Developer Tools. If this was not possible, I guess people would be reverse engineering the Android app or something like that. But when I tried that code, nothing appeared on the screen. My guess is that Brian used the code to extract the keys that weren't necessarily tied to Authy.

I had to adapt the code a little and you can see the result below, but here's what I discovered about Authy's method:

  • They use the exact same algorithm to generate passwords as Google Authenticator and similar (TOTP)
  • The passwords are one digit longer - 7 digits (usually they're 6, with exceptions), but if you've looked at one of the Authy generated passwords already, you probably noticed it too
  • The password validity period is 10 seconds (instead of usual 30). Authy shows 20 seconds, but that means a slightly different thing. Don't substitute this period longer in your Authenticator.
  • Authy's secret keys are in hex already, so they need to be turned back to base32 for working QR codes

So as long as you have an authenticator which can do longer passwords than 6 characters and do custom time periods, then congratulations, you can use the following method. If you are not sure, scan this code with your authenticator to test. Don't forget to delete it afterwards. The code should have 7 digits and should change every 10 seconds.

Known to work:

  • 1Password for OS X
  • 1Password for iOS
  • Google Authenticator

Known not to work:

  • 1Password for Windows (doesn't support other digit counts and timeouts yet)
  • Authy for iOS (doesn't support other timeouts than 30s, the irony!)

Ok, that's nice, but I want to get rid of Authy now

This method has only one gotcha - if you want add a new service that relies on Authy, you will need to run Authy again. I am assuming you know how to use Authy and have some services added already. You can probably get rid of Authy on your phone and log in to Authy on your Chrome app using SMS or keep it permanently disabled under your extensions once you have logged in. In that case set a master password for Authy, stay secure.

  1. Install Authy from Chrome Web Store
  2. Open Authy and log in, so you can see the codes being generated for you
  3. Go to Extensions page in your browser (chrome://extensions/ or Menu -> More tools -> Extensions)
  4. Tick developer mode in top right corner
  5. Find Authy from the list and then click on main.html
  6. Chrome developer tools with Console selected should open. If it didn't, go to Console tab.
  7. Paste following and press enter:
/* base32 */
/*                                                                              
Copyright (c) 2011, Chris Umbel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
function quintetCount(buff) {
  var quintets = Math.floor(buff.length / 5);
  return buff.length % 5 === 0 ? quintets : quintets + 1;
}
encode = function(plain) {
  var i = 0;
  var j = 0;
  var shiftIndex = 0;
  var digit = 0;
  var encoded = new Array(quintetCount(plain) * 8);
  /* byte by byte isn't as pretty as quintet by quintet but tests a bit faster. will have to revisit. */
  while(i < plain.length) {
      var current = plain[i];
      if(shiftIndex > 3) {
          digit = current & (0xff >> shiftIndex);
          shiftIndex = (shiftIndex + 5) % 8;
          digit = (digit << shiftIndex) | ((i + 1 < plain.length) ?
              plain[i + 1] : 0) >> (8 - shiftIndex);
          i++;
      } else {
          digit = (current >> (8 - (shiftIndex + 5))) & 0x1f;
          shiftIndex = (shiftIndex + 5) % 8;
          if(shiftIndex === 0) i++;
      }

      encoded[j] = charTable.charCodeAt(digit);
      j++;
  }
  for(i = j; i < encoded.length; i++) {
      encoded[i] = 0x3d; //'='.charCodeAt(0)
  }
  return encoded.join('');
};
/* base32 end */
function hexToInt(str) {
  var result = [];
  for (var i = 0; i < str.length; i += 2) {
      result.push(parseInt(str.substr(i, 2), 16));
  }
  return result;
}
function hexToB32(str) {
  return encode(hexToInt(str));
}

console.warn("Here's your Authy tokens:");
appManager.getModel().forEach(function(i) {
  var secret = (i.markedForDeletion === false ? i.decryptedSeed : hexToB32(i.secretSeed));
  var totp_uri = 'otpauth://totp/' + encodeURIComponent(i.name) + '?secret=' + secret + '&issuer=' + i.accountType + '&digits=' + i.digits + '&period=10';
  console.group(i.name);
  console.log('TOTP URI: ' + totp_uri);
  console.log('QR code: https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=' + encodeURIComponent(totp_uri));
  console.groupEnd();
});
  1. All your Authy tokens will be displayed in the Console; either copy-paste the TOTP URI, or click the QR code URLs to scan them.
  2. Close opened window and developer tools.
  3. Disable Authy app on Chrome or remove it
  4. Disable Developer mode

Resources used for getting correct codes

Other notes

  • I am not responsible for your actions.
  • I am sure someone has already discovered everything I wrote before, but I couldn't find anything written about it in detail, I didn't invent anything new here
  • The code is a horrible hack, it works for what it does and that's the important bit, improvements are welcome
  • If anyone from Authy reads this - security shouldn't rely on obfuscation or hiding of any sort and should take advantage of freedom of choice where possible. I love the idea of the keys being tied to ones phone number and making this system easy to use for everyone, but please make these URI-s exportable to other applications if users wish to do so - it's possible as demonstrated above and you probably know it. Transparency is what makes this system secure. If you don't wish to do that, then please don't break this method of acquiring keys.
@maciekish
Copy link

Works great except &period=10 is incorrect for most sites. If your OTP does not work, try readding without the &period=10 on the end and it may work.

@binaryoverload
Copy link

So good! Works like a charm

@hensem
Copy link

hensem commented Dec 10, 2018

Where is "main.html"?

@JazzTech
Copy link

Hi, Jacob - I tried this on Authy v2.6.0, and I don't get any usable output. After the Here's your Authy Tokens: I just see undefined

When I single-stepped through the script, it looked like the appManager.getModel().forEach(function(i) might have aborted - it never entered the loop.

JT

Where is "main.html"?

@hensem - If you click on the "background page" hyperlink for the Authy Chrome App, you will see the Chrome DevTools window appear. Click on the Console tab and paste the above code in

@kevindevm
Copy link

here is a gift for any that does not understand
https://i.imgur.com/xivz9j8.gifv

@skymoore
Copy link

On Mac OS getting token urls that have undefined where the key should be.

@skymoore
Copy link

When you first install the chrome app and open it you'll be on the settings page. You need to click the x that looks like it will close the app, but it will actually take you back to your tokens. Select a token and enter your backup password. Afterwards follow steps in the gift from @kevindevm and you have your tokens

@2pravin7
Copy link

Still works like a charm! Thanks for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment