Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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.
@jchv

This comment has been minimized.

Copy link

commented Aug 15, 2017

This is quite awesome! I've really got only one issue: it stuffs 'period=10' onto the URL even if its a traditional Google Authenticator code. Without diving too deeply, I assume that Authy is special-casing its own passwords and just assuming everything else is 30s right now. Would it make sense to add a branch to set it to 10s if there's 7 digits or 30s if there's 6 digits? Right now I'm just manually modifying the ones that are plain Google Authenticator codes and this is working great.

@Daegalus

This comment has been minimized.

Copy link

commented Aug 24, 2017

@jchv

You can just edit the last part of the URL like so:

Original:

+ '&period=10';

Modified:

+ '&period=' + (i.digits === 7 ? '10' : '30');

That should give you 10 seconds if its 7 digits, 30 seconds for everything else.

@mixxolydian

This comment has been minimized.

Copy link

commented Sep 3, 2017

I'm amazed it's this straight forward... I must be doing something wrong though. When I run the code in the console, the keys displayed are outputted as extremely long decimal strings such as:

otpauth://totp/Twitch?secret=7072796979533585787874827150838854827753775775517052616161616161&issuer=Twitch&digits=7&period=10

Would anybody be able to point me in the right direction?

@puddly

This comment has been minimized.

Copy link

commented Oct 9, 2017

If you have a rooted Android phone, I wrote a program to extract secrets from just about every app out there: https://github.com/puddly/android-otp-extractor


@mixxolydian I had the same issue. I'm not entirely sure what caused it, but I rewrote most of the code and it works for me now.

@gboudreau Here's a slight modification to your code that doesn't send your OTP secrets to Google and displays the QR codes directly in the console:

/*! QRious v4.0.2 | (C) 2017 Alasdair Mercer | GPL v3 License
Based on jsqrencode | (C) 2010 tz@execpc.com | GPL v3 License */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.QRious=e()}(this,function(){"use strict";function t(t,e){var n;return"function"==typeof Object.create?n=Object.create(t):(s.prototype=t,n=new s,s.prototype=null),e&&i(!0,n,e),n}function e(e,n,s,r){var o=this;return"string"!=typeof e&&(r=s,s=n,n=e,e=null),"function"!=typeof n&&(r=s,s=n,n=function(){return o.apply(this,arguments)}),i(!1,n,o,r),n.prototype=t(o.prototype,s),n.prototype.constructor=n,n.class_=e||o.class_,n.super_=o,n}function i(t,e,i){for(var n,s,a=0,h=(i=o.call(arguments,2)).length;a<h;a++){s=i[a];for(n in s)t&&!r.call(s,n)||(e[n]=s[n])}}function n(){}var s=function(){},r=Object.prototype.hasOwnProperty,o=Array.prototype.slice,a=e;n.class_="Nevis",n.super_=Object,n.extend=a;var h=n,f=h.extend(function(t,e,i){this.qrious=t,this.element=e,this.element.qrious=t,this.enabled=Boolean(i)},{draw:function(t){},getElement:function(){return this.enabled||(this.enabled=!0,this.render()),this.element},getModuleSize:function(t){var e=this.qrious,i=e.padding||0,n=Math.floor((e.size-2*i)/t.width);return Math.max(1,n)},getOffset:function(t){var e=this.qrious,i=e.padding;if(null!=i)return i;var n=this.getModuleSize(t),s=Math.floor((e.size-n*t.width)/2);return Math.max(0,s)},render:function(t){this.enabled&&(this.resize(),this.reset(),this.draw(t))},reset:function(){},resize:function(){}}),c=f.extend({draw:function(t){var e,i,n=this.qrious,s=this.getModuleSize(t),r=this.getOffset(t),o=this.element.getContext("2d");for(o.fillStyle=n.foreground,o.globalAlpha=n.foregroundAlpha,e=0;e<t.width;e++)for(i=0;i<t.width;i++)t.buffer[i*t.width+e]&&o.fillRect(s*e+r,s*i+r,s,s)},reset:function(){var t=this.qrious,e=this.element.getContext("2d"),i=t.size;e.lineWidth=1,e.clearRect(0,0,i,i),e.fillStyle=t.background,e.globalAlpha=t.backgroundAlpha,e.fillRect(0,0,i,i)},resize:function(){var t=this.element;t.width=t.height=this.qrious.size}}),u=h.extend(null,{BLOCK:[0,11,15,19,23,27,31,16,18,20,22,24,26,28,20,22,24,24,26,28,28,22,24,24,26,26,28,28,24,24,26,26,26,28,28,24,26,26,26,28,28]}),l=h.extend(null,{BLOCKS:[1,0,19,7,1,0,16,10,1,0,13,13,1,0,9,17,1,0,34,10,1,0,28,16,1,0,22,22,1,0,16,28,1,0,55,15,1,0,44,26,2,0,17,18,2,0,13,22,1,0,80,20,2,0,32,18,2,0,24,26,4,0,9,16,1,0,108,26,2,0,43,24,2,2,15,18,2,2,11,22,2,0,68,18,4,0,27,16,4,0,19,24,4,0,15,28,2,0,78,20,4,0,31,18,2,4,14,18,4,1,13,26,2,0,97,24,2,2,38,22,4,2,18,22,4,2,14,26,2,0,116,30,3,2,36,22,4,4,16,20,4,4,12,24,2,2,68,18,4,1,43,26,6,2,19,24,6,2,15,28,4,0,81,20,1,4,50,30,4,4,22,28,3,8,12,24,2,2,92,24,6,2,36,22,4,6,20,26,7,4,14,28,4,0,107,26,8,1,37,22,8,4,20,24,12,4,11,22,3,1,115,30,4,5,40,24,11,5,16,20,11,5,12,24,5,1,87,22,5,5,41,24,5,7,24,30,11,7,12,24,5,1,98,24,7,3,45,28,15,2,19,24,3,13,15,30,1,5,107,28,10,1,46,28,1,15,22,28,2,17,14,28,5,1,120,30,9,4,43,26,17,1,22,28,2,19,14,28,3,4,113,28,3,11,44,26,17,4,21,26,9,16,13,26,3,5,107,28,3,13,41,26,15,5,24,30,15,10,15,28,4,4,116,28,17,0,42,26,17,6,22,28,19,6,16,30,2,7,111,28,17,0,46,28,7,16,24,30,34,0,13,24,4,5,121,30,4,14,47,28,11,14,24,30,16,14,15,30,6,4,117,30,6,14,45,28,11,16,24,30,30,2,16,30,8,4,106,26,8,13,47,28,7,22,24,30,22,13,15,30,10,2,114,28,19,4,46,28,28,6,22,28,33,4,16,30,8,4,122,30,22,3,45,28,8,26,23,30,12,28,15,30,3,10,117,30,3,23,45,28,4,31,24,30,11,31,15,30,7,7,116,30,21,7,45,28,1,37,23,30,19,26,15,30,5,10,115,30,19,10,47,28,15,25,24,30,23,25,15,30,13,3,115,30,2,29,46,28,42,1,24,30,23,28,15,30,17,0,115,30,10,23,46,28,10,35,24,30,19,35,15,30,17,1,115,30,14,21,46,28,29,19,24,30,11,46,15,30,13,6,115,30,14,23,46,28,44,7,24,30,59,1,16,30,12,7,121,30,12,26,47,28,39,14,24,30,22,41,15,30,6,14,121,30,6,34,47,28,46,10,24,30,2,64,15,30,17,4,122,30,29,14,46,28,49,10,24,30,24,46,15,30,4,18,122,30,13,32,46,28,48,14,24,30,42,32,15,30,20,4,117,30,40,7,47,28,43,22,24,30,10,67,15,30,19,6,118,30,18,31,47,28,34,34,24,30,20,61,15,30],FINAL_FORMAT:[30660,29427,32170,30877,26159,25368,27713,26998,21522,20773,24188,23371,17913,16590,20375,19104,13663,12392,16177,14854,9396,8579,11994,11245,5769,5054,7399,6608,1890,597,3340,2107],LEVELS:{L:1,M:2,Q:3,H:4}}),_=h.extend(null,{EXPONENT:[1,2,4,8,16,32,64,128,29,58,116,232,205,135,19,38,76,152,45,90,180,117,234,201,143,3,6,12,24,48,96,192,157,39,78,156,37,74,148,53,106,212,181,119,238,193,159,35,70,140,5,10,20,40,80,160,93,186,105,210,185,111,222,161,95,190,97,194,153,47,94,188,101,202,137,15,30,60,120,240,253,231,211,187,107,214,177,127,254,225,223,163,91,182,113,226,217,175,67,134,17,34,68,136,13,26,52,104,208,189,103,206,129,31,62,124,248,237,199,147,59,118,236,197,151,51,102,204,133,23,46,92,184,109,218,169,79,158,33,66,132,21,42,84,168,77,154,41,82,164,85,170,73,146,57,114,228,213,183,115,230,209,191,99,198,145,63,126,252,229,215,179,123,246,241,255,227,219,171,75,150,49,98,196,149,55,110,220,165,87,174,65,130,25,50,100,200,141,7,14,28,56,112,224,221,167,83,166,81,162,89,178,121,242,249,239,195,155,43,86,172,69,138,9,18,36,72,144,61,122,244,245,247,243,251,235,203,139,11,22,44,88,176,125,250,233,207,131,27,54,108,216,173,71,142,0],LOG:[255,0,1,25,2,50,26,198,3,223,51,238,27,104,199,75,4,100,224,14,52,141,239,129,28,193,105,248,200,8,76,113,5,138,101,47,225,36,15,33,53,147,142,218,240,18,130,69,29,181,194,125,106,39,249,185,201,154,9,120,77,228,114,166,6,191,139,98,102,221,48,253,226,152,37,179,16,145,34,136,54,208,148,206,143,150,219,189,241,210,19,92,131,56,70,64,30,66,182,163,195,72,126,110,107,58,40,84,250,133,186,61,202,94,155,159,10,21,121,43,78,212,229,172,115,243,167,87,7,112,192,247,140,128,99,13,103,74,222,237,49,197,254,24,227,165,153,119,38,184,180,124,17,68,146,217,35,32,137,46,55,63,209,91,149,188,207,205,144,135,151,178,220,252,190,97,242,86,211,171,20,42,93,158,132,60,57,83,71,109,65,162,31,45,67,216,183,123,164,118,196,23,73,236,127,12,111,246,108,161,59,82,41,157,85,170,251,96,134,177,187,204,62,90,203,89,95,176,156,169,160,81,11,245,22,235,122,117,44,215,79,174,213,233,230,231,173,232,116,214,244,234,168,80,88,175]}),d=h.extend(null,{BLOCK:[3220,1468,2713,1235,3062,1890,2119,1549,2344,2936,1117,2583,1330,2470,1667,2249,2028,3780,481,4011,142,3098,831,3445,592,2517,1776,2234,1951,2827,1070,2660,1345,3177]}),v=h.extend(function(t){var e,i,n,s,r,o=t.value.length;for(this._badness=[],this._level=l.LEVELS[t.level],this._polynomial=[],this._value=t.value,this._version=0,this._stringBuffer=[];this._version<40&&(this._version++,n=4*(this._level-1)+16*(this._version-1),s=l.BLOCKS[n++],r=l.BLOCKS[n++],e=l.BLOCKS[n++],i=l.BLOCKS[n],n=e*(s+r)+r-3+(this._version<=9),!(o<=n)););this._dataBlock=e,this._eccBlock=i,this._neccBlock1=s,this._neccBlock2=r;var a=this.width=17+4*this._version;this.buffer=v._createArray(a*a),this._ecc=v._createArray(e+(e+i)*(s+r)+r),this._mask=v._createArray((a*(a+1)+1)/2),this._insertFinders(),this._insertAlignments(),this.buffer[8+a*(a-8)]=1,this._insertTimingGap(),this._reverseMask(),this._insertTimingRowAndColumn(),this._insertVersion(),this._syncMask(),this._convertBitStream(o),this._calculatePolynomial(),this._appendEccToData(),this._interleaveBlocks(),this._pack(),this._finish()},{_addAlignment:function(t,e){var i,n=this.buffer,s=this.width;for(n[t+s*e]=1,i=-2;i<2;i++)n[t+i+s*(e-2)]=1,n[t-2+s*(e+i+1)]=1,n[t+2+s*(e+i)]=1,n[t+i+1+s*(e+2)]=1;for(i=0;i<2;i++)this._setMask(t-1,e+i),this._setMask(t+1,e-i),this._setMask(t-i,e-1),this._setMask(t+i,e+1)},_appendData:function(t,e,i,n){var s,r,o,a=this._polynomial,h=this._stringBuffer;for(r=0;r<n;r++)h[i+r]=0;for(r=0;r<e;r++){if(255!==(s=_.LOG[h[t+r]^h[i]]))for(o=1;o<n;o++)h[i+o-1]=h[i+o]^_.EXPONENT[v._modN(s+a[n-o])];else for(o=i;o<i+n;o++)h[o]=h[o+1];h[i+n-1]=255===s?0:_.EXPONENT[v._modN(s+a[0])]}},_appendEccToData:function(){var t,e=0,i=this._dataBlock,n=this._calculateMaxLength(),s=this._eccBlock;for(t=0;t<this._neccBlock1;t++)this._appendData(e,i,n,s),e+=i,n+=s;for(t=0;t<this._neccBlock2;t++)this._appendData(e,i+1,n,s),e+=i+1,n+=s},_applyMask:function(t){var e,i,n,s,r=this.buffer,o=this.width;switch(t){case 0:for(s=0;s<o;s++)for(n=0;n<o;n++)n+s&1||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 1:for(s=0;s<o;s++)for(n=0;n<o;n++)1&s||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 2:for(s=0;s<o;s++)for(e=0,n=0;n<o;n++,e++)3===e&&(e=0),e||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 3:for(i=0,s=0;s<o;s++,i++)for(3===i&&(i=0),e=i,n=0;n<o;n++,e++)3===e&&(e=0),e||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 4:for(s=0;s<o;s++)for(e=0,i=s>>1&1,n=0;n<o;n++,e++)3===e&&(e=0,i=!i),i||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 5:for(i=0,s=0;s<o;s++,i++)for(3===i&&(i=0),e=0,n=0;n<o;n++,e++)3===e&&(e=0),(n&s&1)+!(!e|!i)||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 6:for(i=0,s=0;s<o;s++,i++)for(3===i&&(i=0),e=0,n=0;n<o;n++,e++)3===e&&(e=0),(n&s&1)+(e&&e===i)&1||this._isMasked(n,s)||(r[n+s*o]^=1);break;case 7:for(i=0,s=0;s<o;s++,i++)for(3===i&&(i=0),e=0,n=0;n<o;n++,e++)3===e&&(e=0),(e&&e===i)+(n+s&1)&1||this._isMasked(n,s)||(r[n+s*o]^=1)}},_calculateMaxLength:function(){return this._dataBlock*(this._neccBlock1+this._neccBlock2)+this._neccBlock2},_calculatePolynomial:function(){var t,e,i=this._eccBlock,n=this._polynomial;for(n[0]=1,t=0;t<i;t++){for(n[t+1]=1,e=t;e>0;e--)n[e]=n[e]?n[e-1]^_.EXPONENT[v._modN(_.LOG[n[e]]+t)]:n[e-1];n[0]=_.EXPONENT[v._modN(_.LOG[n[0]]+t)]}for(t=0;t<=i;t++)n[t]=_.LOG[n[t]]},_checkBadness:function(){var t,e,i,n,s,r=0,o=this._badness,a=this.buffer,h=this.width;for(s=0;s<h-1;s++)for(n=0;n<h-1;n++)(a[n+h*s]&&a[n+1+h*s]&&a[n+h*(s+1)]&&a[n+1+h*(s+1)]||!(a[n+h*s]||a[n+1+h*s]||a[n+h*(s+1)]||a[n+1+h*(s+1)]))&&(r+=v.N2);var f=0;for(s=0;s<h;s++){for(i=0,o[0]=0,t=0,n=0;n<h;n++)t===(e=a[n+h*s])?o[i]++:o[++i]=1,f+=(t=e)?1:-1;r+=this._getBadness(i)}f<0&&(f=-f);var c=0,u=f;for(u+=u<<2,u<<=1;u>h*h;)u-=h*h,c++;for(r+=c*v.N4,n=0;n<h;n++){for(i=0,o[0]=0,t=0,s=0;s<h;s++)t===(e=a[n+h*s])?o[i]++:o[++i]=1,t=e;r+=this._getBadness(i)}return r},_convertBitStream:function(t){var e,i,n=this._ecc,s=this._version;for(i=0;i<t;i++)n[i]=this._value.charCodeAt(i);var r=this._stringBuffer=n.slice(),o=this._calculateMaxLength();t>=o-2&&(t=o-2,s>9&&t--);var a=t;if(s>9){for(r[a+2]=0,r[a+3]=0;a--;)e=r[a],r[a+3]|=255&e<<4,r[a+2]=e>>4;r[2]|=255&t<<4,r[1]=t>>4,r[0]=64|t>>12}else{for(r[a+1]=0,r[a+2]=0;a--;)e=r[a],r[a+2]|=255&e<<4,r[a+1]=e>>4;r[1]|=255&t<<4,r[0]=64|t>>4}for(a=t+3-(s<10);a<o;)r[a++]=236,r[a++]=17},_getBadness:function(t){var e,i=0,n=this._badness;for(e=0;e<=t;e++)n[e]>=5&&(i+=v.N1+n[e]-5);for(e=3;e<t-1;e+=2)n[e-2]===n[e+2]&&n[e+2]===n[e-1]&&n[e-1]===n[e+1]&&3*n[e-1]===n[e]&&(0===n[e-3]||e+3>t||3*n[e-3]>=4*n[e]||3*n[e+3]>=4*n[e])&&(i+=v.N3);return i},_finish:function(){this._stringBuffer=this.buffer.slice();var t,e,i=0,n=3e4;for(e=0;e<8&&(this._applyMask(e),(t=this._checkBadness())<n&&(n=t,i=e),7!==i);e++)this.buffer=this._stringBuffer.slice();i!==e&&this._applyMask(i),n=l.FINAL_FORMAT[i+(this._level-1<<3)];var s=this.buffer,r=this.width;for(e=0;e<8;e++,n>>=1)1&n&&(s[r-1-e+8*r]=1,e<6?s[8+r*e]=1:s[8+r*(e+1)]=1);for(e=0;e<7;e++,n>>=1)1&n&&(s[8+r*(r-7+e)]=1,e?s[6-e+8*r]=1:s[7+8*r]=1)},_interleaveBlocks:function(){var t,e,i=this._dataBlock,n=this._ecc,s=this._eccBlock,r=0,o=this._calculateMaxLength(),a=this._neccBlock1,h=this._neccBlock2,f=this._stringBuffer;for(t=0;t<i;t++){for(e=0;e<a;e++)n[r++]=f[t+e*i];for(e=0;e<h;e++)n[r++]=f[a*i+t+e*(i+1)]}for(e=0;e<h;e++)n[r++]=f[a*i+t+e*(i+1)];for(t=0;t<s;t++)for(e=0;e<a+h;e++)n[r++]=f[o+t+e*s];this._stringBuffer=n},_insertAlignments:function(){var t,e,i,n=this._version,s=this.width;if(n>1)for(t=u.BLOCK[n],i=s-7;;){for(e=s-7;e>t-3&&(this._addAlignment(e,i),!(e<t));)e-=t;if(i<=t+9)break;i-=t,this._addAlignment(6,i),this._addAlignment(i,6)}},_insertFinders:function(){var t,e,i,n,s=this.buffer,r=this.width;for(t=0;t<3;t++){for(e=0,n=0,1===t&&(e=r-7),2===t&&(n=r-7),s[n+3+r*(e+3)]=1,i=0;i<6;i++)s[n+i+r*e]=1,s[n+r*(e+i+1)]=1,s[n+6+r*(e+i)]=1,s[n+i+1+r*(e+6)]=1;for(i=1;i<5;i++)this._setMask(n+i,e+1),this._setMask(n+1,e+i+1),this._setMask(n+5,e+i),this._setMask(n+i+1,e+5);for(i=2;i<4;i++)s[n+i+r*(e+2)]=1,s[n+2+r*(e+i+1)]=1,s[n+4+r*(e+i)]=1,s[n+i+1+r*(e+4)]=1}},_insertTimingGap:function(){var t,e,i=this.width;for(e=0;e<7;e++)this._setMask(7,e),this._setMask(i-8,e),this._setMask(7,e+i-7);for(t=0;t<8;t++)this._setMask(t,7),this._setMask(t+i-8,7),this._setMask(t,i-8)},_insertTimingRowAndColumn:function(){var t,e=this.buffer,i=this.width;for(t=0;t<i-14;t++)1&t?(this._setMask(8+t,6),this._setMask(6,8+t)):(e[8+t+6*i]=1,e[6+i*(8+t)]=1)},_insertVersion:function(){var t,e,i,n,s=this.buffer,r=this._version,o=this.width;if(r>6)for(t=d.BLOCK[r-7],e=17,i=0;i<6;i++)for(n=0;n<3;n++,e--)1&(e>11?r>>e-12:t>>e)?(s[5-i+o*(2-n+o-11)]=1,s[2-n+o-11+o*(5-i)]=1):(this._setMask(5-i,2-n+o-11),this._setMask(2-n+o-11,5-i))},_isMasked:function(t,e){var i=v._getMaskBit(t,e);return 1===this._mask[i]},_pack:function(){var t,e,i,n=1,s=1,r=this.width,o=r-1,a=r-1,h=(this._dataBlock+this._eccBlock)*(this._neccBlock1+this._neccBlock2)+this._neccBlock2;for(e=0;e<h;e++)for(t=this._stringBuffer[e],i=0;i<8;i++,t<<=1){128&t&&(this.buffer[o+r*a]=1);do{s?o--:(o++,n?0!==a?a--:(n=!n,6===(o-=2)&&(o--,a=9)):a!==r-1?a++:(n=!n,6===(o-=2)&&(o--,a-=8))),s=!s}while(this._isMasked(o,a))}},_reverseMask:function(){var t,e,i=this.width;for(t=0;t<9;t++)this._setMask(t,8);for(t=0;t<8;t++)this._setMask(t+i-8,8),this._setMask(8,t);for(e=0;e<7;e++)this._setMask(8,e+i-7)},_setMask:function(t,e){var i=v._getMaskBit(t,e);this._mask[i]=1},_syncMask:function(){var t,e,i=this.width;for(e=0;e<i;e++)for(t=0;t<=e;t++)this.buffer[t+i*e]&&this._setMask(t,e)}},{_createArray:function(t){var e,i=[];for(e=0;e<t;e++)i[e]=0;return i},_getMaskBit:function(t,e){var i;return t>e&&(i=t,t=e,e=i),i=e,i+=e*e,i>>=1,i+=t},_modN:function(t){for(;t>=255;)t=((t-=255)>>8)+(255&t);return t},N1:3,N2:3,N3:40,N4:10}),p=v,m=f.extend({draw:function(){this.element.src=this.qrious.toDataURL()},reset:function(){this.element.src=""},resize:function(){var t=this.element;t.width=t.height=this.qrious.size}}),g=h.extend(function(t,e,i,n){this.name=t,this.modifiable=Boolean(e),this.defaultValue=i,this._valueTransformer=n},{transform:function(t){var e=this._valueTransformer;return"function"==typeof e?e(t,this):t}}),k=h.extend(null,{abs:function(t){return null!=t?Math.abs(t):null},hasOwn:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},noop:function(){},toUpperCase:function(t){return null!=t?t.toUpperCase():null}}),w=h.extend(function(t){this.options={},t.forEach(function(t){this.options[t.name]=t},this)},{exists:function(t){return null!=this.options[t]},get:function(t,e){return w._get(this.options[t],e)},getAll:function(t){var e,i=this.options,n={};for(e in i)k.hasOwn(i,e)&&(n[e]=w._get(i[e],t));return n},init:function(t,e,i){"function"!=typeof i&&(i=k.noop);var n,s;for(n in this.options)k.hasOwn(this.options,n)&&(s=this.options[n],w._set(s,s.defaultValue,e),w._createAccessor(s,e,i));this._setAll(t,e,!0)},set:function(t,e,i){return this._set(t,e,i)},setAll:function(t,e){return this._setAll(t,e)},_set:function(t,e,i,n){var s=this.options[t];if(!s)throw new Error("Invalid option: "+t);if(!s.modifiable&&!n)throw new Error("Option cannot be modified: "+t);return w._set(s,e,i)},_setAll:function(t,e,i){if(!t)return!1;var n,s=!1;for(n in t)k.hasOwn(t,n)&&this._set(n,t[n],e,i)&&(s=!0);return s}},{_createAccessor:function(t,e,i){var n={get:function(){return w._get(t,e)}};t.modifiable&&(n.set=function(n){w._set(t,n,e)&&i(n,t)}),Object.defineProperty(e,t.name,n)},_get:function(t,e){return e["_"+t.name]},_set:function(t,e,i){var n="_"+t.name,s=i[n],r=t.transform(null!=e?e:t.defaultValue);return i[n]=r,r!==s}}),M=w,b=h.extend(function(){this._services={}},{getService:function(t){var e=this._services[t];if(!e)throw new Error("Service is not being managed with name: "+t);return e},setService:function(t,e){if(this._services[t])throw new Error("Service is already managed with name: "+t);e&&(this._services[t]=e)}}),B=new M([new g("background",!0,"white"),new g("backgroundAlpha",!0,1,k.abs),new g("element"),new g("foreground",!0,"black"),new g("foregroundAlpha",!0,1,k.abs),new g("level",!0,"L",k.toUpperCase),new g("mime",!0,"image/png"),new g("padding",!0,null,k.abs),new g("size",!0,100,k.abs),new g("value",!0,"")]),y=new b,O=h.extend(function(t){B.init(t,this,this.update.bind(this));var e=B.get("element",this),i=y.getService("element"),n=e&&i.isCanvas(e)?e:i.createCanvas(),s=e&&i.isImage(e)?e:i.createImage();this._canvasRenderer=new c(this,n,!0),this._imageRenderer=new m(this,s,s===e),this.update()},{get:function(){return B.getAll(this)},set:function(t){B.setAll(t,this)&&this.update()},toDataURL:function(t){return this.canvas.toDataURL(t||this.mime)},update:function(){var t=new p({level:this.level,value:this.value});this._canvasRenderer.render(t),this._imageRenderer.render(t)}},{use:function(t){y.setService(t.getName(),t)}});Object.defineProperties(O.prototype,{canvas:{get:function(){return this._canvasRenderer.getElement()}},image:{get:function(){return this._imageRenderer.getElement()}}});var A=O,L=h.extend({getName:function(){}}).extend({createCanvas:function(){},createImage:function(){},getName:function(){return"element"},isCanvas:function(t){},isImage:function(t){}}).extend({createCanvas:function(){return document.createElement("canvas")},createImage:function(){return document.createElement("img")},isCanvas:function(t){return t instanceof HTMLCanvasElement},isImage:function(t){return t instanceof HTMLImageElement}});return A.use(new L),A});


// 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;
}

// Based on https://github.com/adriancooney/console.image
function console_image(url, size) {
	console.log("%c+", "font-size: 1px; padding: " + Math.floor(size / 2) + "px " + Math.floor(size / 2) + "px; line-height: " + size + "px; background: url(" + url + "); color: transparent;");
}


appManager.getModel().forEach(function(i) {
	let qr_size = 500;

	let secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.secretSeed));
	let period = (i.digits === 7 ? 10 : 30);
	let totp_uri = `otpauth://totp/${encodeURIComponent(i.name)}?secret=${secret}&digits=${i.digits}&period=${period}`;

	console.group(i.name);
		console.log('TOTP secret:', secret);
		console.log('TOTP URI:', totp_uri);

		let url = (new QRious({value: totp_uri, size: qr_size})).toDataURL();
		console_image(url, qr_size);
	console.groupEnd();
});
@irowboat

This comment has been minimized.

Copy link

commented Oct 13, 2017

YES! @puddly, you're my hero! Liberating my Humble Bundle 2FA from Authy was giving me fits of despair, until just now, when your code worked with Authy 2.4.0!

@w4

This comment has been minimized.

Copy link

commented Nov 19, 2017

You're a legend @puddly

@judge2020

This comment has been minimized.

Copy link

commented Nov 21, 2017

7-digit works with Lastpass authenticator.

@ColtonBeery

This comment has been minimized.

Copy link

commented Dec 17, 2017

This worked perfectly when trying to add all my TOTP codes from Authy into Keepass, which I use have used for password management for a long time. It was driving me nuts needing to use a different app for TOTP when Keepass has the functionality for it. The only thing is that for some reason the timing doesn't match exactly between Authy and Keepass - Keepass would say the codes expired 4 or 5 seconds sooner than Authy did.

@DanDawson

This comment has been minimized.

Copy link

commented Jan 11, 2018

Just to help anyone else who might have run into what I saw:

With 1Password you can't simply copy the TOTP Secret from the results, you'll want to copy the TOTP URI which defines the details of 7-digits and 10 second password. If you just paste in the regular secret you'll get 6-digit codes.

Yeah, if I'd followed the instructions it wouldn't be an issue... but sometimes we skip reading instructions and jump straight to code ;-)

@gergesh

This comment has been minimized.

Copy link

commented Jan 27, 2018

I'm not seeing a button for main.html (only for the background page), on linux and using chromium. Anyone else experiencing this?

Edit: Same with chrome. I've noticed that the button does appear for (a few) other apps, though. Is it possible that they've changed their app with 2.5?

@paralel-github

This comment has been minimized.

Copy link

commented Jan 31, 2018

Works great to import Authy OTP to 1Password! Thanks!! 👍

@jamji

This comment has been minimized.

Copy link

commented Feb 17, 2018

Great work man!

@AeliusSaionji

This comment has been minimized.

Copy link

commented Feb 18, 2018

As of right now with Authy 2.5.0, I'm unable to generate valid codes for Twitch using andOTP for android.

@gergesh follow the directions more carefully

  1. Open Authy and log in, so you can see the codes being generated for you
@AeliusSaionji

This comment has been minimized.

Copy link

commented Feb 18, 2018

Puddly's worked for me though. Thanks!

@ddxv

This comment has been minimized.

Copy link

commented May 24, 2018

Puddly's worked great for me. For anyone who found it confusing, there is both an Authy extension & app. Please make sure you use the Authy App which is also found in chrome://extensions. The extension only contains background.html while the app has both main.html and background.html

@dartraiden

This comment has been minimized.

Copy link

commented Jul 7, 2018

Also, you should leave Authy App opened to see a link to main.html on chrome://extensions/?id=gaedmjdfmmahhbjefcbgaolhhanlaolb

@louiszuckerman

This comment has been minimized.

Copy link

commented Aug 30, 2018

I ran this in chrome 68 and the secret produced in the URL was a very long integer, not base32. i used a hex to base32 converter and plugged the result into the QR URL, replacing the long integer, and that yielded a working 1Password generator.

I got the raw hex key using the chrome debugger running appManager.getModel() in the console and inspecting the output.

Patch to fix the code above...

Line 49: change encoded[j] = charTable.charCodeAt(digit); to encoded[j] = charTable.charAt(digit);
Line 53: change encoded[i] = 0x3d; //'='.charCodeAt(0) to encoded[i] = '=';

Fork with the changes above: https://gist.github.com/semiosis/2dd4fddf8097ce89594bb33426ab5e23#ok-thats-nice-but-i-want-to-get-rid-of-authy-now

@colemickens

This comment has been minimized.

Copy link

commented Nov 3, 2018

I've further modified @semiosis's version to output a json object instead of plain text. This makes it easier to copy/paste from the console into a file and/or possibly import it into something like pass.

@veritas06

This comment has been minimized.

Copy link

commented Nov 11, 2018

Am I missing something here? Each time I try to input the code to the Console, I get back:

Uncaught ReferenceError: appManager is not defined
    at <anonymous>:70:1

EDIT: I wasn't able to get this script working, but I did find a post on GBATemp that gave the path of where the Authy Android app stores the secrets: /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml

@schwark

This comment has been minimized.

Copy link

commented Dec 11, 2018

The 7 digit codes generated by 1password with these QR codes are different than the ones generated by Authy? Not sure if I am doing something wrong.

@asulwer

This comment has been minimized.

Copy link

commented Dec 17, 2018

secret=undefined

@pandalion98

This comment has been minimized.

Copy link

commented Dec 25, 2018

Am I missing something here? Each time I try to input the code to the Console, I get back:

Uncaught ReferenceError: appManager is not defined
    at <anonymous>:70:1

EDIT: I wasn't able to get this script working, but I did find a post on GBATemp that gave the path of where the Authy Android app stores the secrets: /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml

I found that Authy on Chrome actually has two components, "Authy Chrome Extension" which shows up on your upper right menu bar, and the "Authy" Chrome app itself which pops up as a separate window. I encountered this error when I tried it on Authy Chrome Extension.

The one in question here is the Chrome app itself. Scroll down to the bottom. It should be listed under "Chrome Apps".

Under "Inspect views", I have two main.htmls listed. Either worked.

@peterzen

This comment has been minimized.

Copy link

commented Jan 8, 2019

I'm getting the secret=undefined as well. Has authy perhaps changed the encoding on their end?

@HybridGlucose

This comment has been minimized.

Copy link

commented Jan 15, 2019

I'm getting the secret=undefined as well. Has authy perhaps changed the encoding on their end?

You first need to click any item you have,than will ask your password to decrypt and try export again.

@felixfischer

This comment has been minimized.

Copy link

commented Jan 28, 2019

Awesome, thank you!

@nmurthy

This comment has been minimized.

Copy link

commented Jan 30, 2019

I'm getting the secret=undefined as well. Has authy perhaps changed the encoding on their end?

I actually was able to fix this with a minor change: https://gist.github.com/nmurthy/a5e7107671cf04529ac5f3a7471f2357

(a couple other small changes in there as well. call getTotps() in the console to use)

@JazzTech

This comment has been minimized.

Copy link

commented Feb 25, 2019

@gboudreau, @puddly, and @nmurthy - Thanks for creating these scripts! I got everything to work.

I have put together updated instructions to get your Authy secret keys and QR codes:

  1. Start the Authy Chrome App
  2. Unlock Authy with your password
  3. Go to @nmurthy 's updated script github page. Select all the code, and press Ctrl-C to copy the getTotps.js script into your clipboard.
  4. In your Chrome browser, click on the menu dots in the top right corner, and select: More tools >> Extensions
  5. Scroll down to the bottom of the Extensions page, and look for the Authy 2.6.0 Chrome App (ID: gaedmjdfmmahhbjefcbgaolhhanlaolb).
  6. Click on the Details button. This will open up the Authy Chrome App extension page.
  7. Under the Inspect views section, you should see two hyperlinks: background page and main.html. Click on the main.html hyperlink.
  8. This will open up the Chrome DevTools window for main.html. There should be a Console window across the bottom 1/3 of the DevTools window.
  9. Click your cursor in the Console window next to the ">".
  10. Press Ctrl-P to paste the getTotps.js script into the Console.
  11. In the Console window, press Enter
  12. In the Console window, type getTotps() and then press Enter
  13. You should now see each of your Authy keys listed in the Console window. The secret key value is after the secret= and before the & in the TOTP URI value. The QR code hyperlink will display a QR code that you can scan in Authy (or other) authenticator application.

Thanks again ... JazzTech

@salt-lick

This comment has been minimized.

Copy link

commented Feb 28, 2019

@puddly : Thank you for the working script. It's really annoying that Twilio ( that owns Authy ) won't give you a QR code to scan to add their TOTP to 1Password. This helped me out more than you know.

@jordanbtucker

This comment has been minimized.

Copy link

commented Mar 5, 2019

This isn't working for me. It keeps giving me a URL with a period of 10, but the code is clearly changing every 20 seconds. Changing the period to 20 is not giving me correct codes either.

@anthonymvicente

This comment has been minimized.

Copy link

commented Mar 6, 2019

This isn't working for me. It keeps giving me a URL with a period of 10, but the code is clearly changing every 20 seconds. Changing the period to 20 is not giving me correct codes either.

I'm having this same issue, trying to move my twitch account over and I can't. The QR code imports into andOTP but doesn't work properly, and trying to use the secret with various settings doesn't either.

@jimsug

This comment has been minimized.

Copy link

commented Mar 9, 2019

@jordanbtucker @anthonymvicente I found that the TOTP generated by KeePass matched the TOTP generated by Authy for only the first ten seconds of its "twenty" second validity (I suspect the extended validity is just a tolerance measure to account for possible cases where time on the device doesn't exactly match time on the server).

However, I have just tested it and can confirm that the codes are valid, even though the Authy app displays a new code only every twenty seconds.

@lightmaster

This comment has been minimized.

Copy link

commented Mar 30, 2019

@JazzTech's version didn't work for me, just gave me an empty box with no code, but @puddly's did https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93#gistcomment-2224133 did. BTW, Lastpass Authenticator does work with this, both with @puddly's QR code, and by manually typing in the secret. Lastpass now lets you click the advanced options button on a manually created code and you can set it to 7 digits and 10 seconds then, although I've had it successfully work after more than 15 second multiple times.

@h-town

This comment has been minimized.

Copy link

commented Jun 3, 2019

puddly ftw

@LiquidPL

This comment has been minimized.

Copy link

commented Jun 11, 2019

I've tried it several times, but Google Authenticator is definitely not working. It simply ignores the digits and period options, and just assumes 6/30s.

@alexzorin

This comment has been minimized.

Copy link

commented Jul 13, 2019

I've written a standalone library & program to export TOTP URIs from Authy without having to run/modify the Authy Chrome app. I didn't have any 7 digit seeds to test with, so if anybody wants to drop by and leave feedback on how that should work, that'd be cool.

@jace

This comment has been minimized.

Copy link

commented Jul 22, 2019

@alexzorin How does one use your code assuming no familiarity with Go? I copied a command out of your .travis.yml and ran it and got this:

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o authy-export -ldflags "-s -w" cmd/authy-export/authy-export.go 
cmd/authy-export/authy-export.go:16:2: cannot find package "github.com/alexzorin/authy" in any of:
	/usr/lib/go-1.10/src/github.com/alexzorin/authy (from $GOROOT)
	/home/jackerhack/go/src/github.com/alexzorin/authy (from $GOPATH)
cmd/authy-export/authy-export.go:17:2: cannot find package "golang.org/x/crypto/ssh/terminal" in any of:
	/usr/lib/go-1.10/src/golang.org/x/crypto/ssh/terminal (from $GOROOT)
	/home/jackerhack/go/src/golang.org/x/crypto/ssh/terminal (from $GOPATH)
@jace

This comment has been minimized.

Copy link

commented Jul 22, 2019

@alexzorin I managed to get your code working on Ubuntu 18.04 by moving my paths around to match, installing the two dependencies (go get <path>), and with the following patch:

diff --git a/cmd/authy-export/authy-export.go b/cmd/authy-export/authy-export.go
index b542138..18b4748 100644
--- a/cmd/authy-export/authy-export.go
+++ b/cmd/authy-export/authy-export.go
@@ -207,9 +207,10 @@ func loadExistingDeviceRegistration() (deviceRegistration, error) {
 
 func configPath() (string, error) {
        // TODO: In Go 1.13, use os.UserConfigDir()
-       regrPath, err := os.UserHomeDir()
-       if err != nil {
-               return "", err
-       }
+       regrPath := os.Getenv("HOME")
        return filepath.Join(regrPath, "authy-go.json"), nil
 }

I finally have a copy of my tokens. Yay!

@alexzorin

This comment has been minimized.

Copy link

commented Jul 23, 2019

How does one use your code assuming no familiarity with Go

Glad to hear you got it working. You can also just download a standalone binary to avoid messing about with compilers.

@kadaliao

This comment has been minimized.

Copy link

commented Jul 23, 2019

How does one use your code assuming no familiarity with Go

Glad to hear you got it working. You can also just download a standalone binary to avoid messing about with compilers.

you are so great!

@kmpoppe

This comment has been minimized.

Copy link

commented Aug 25, 2019

I was also having the same issue with trying to move Twitch from Authy to 1P - yet the code in my 1P Android app (it displays a code, not like the Windows App 😡 ) has nothing to do with either the code generated in the Chrome extension nor in the Authy App on the phone. I checked the system time (in case it was horribly off) but that wasn't the case either.
@lightmaster 's comment https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93#gistcomment-2875862 was the solution there.

@kmpoppe

This comment has been minimized.

Copy link

commented Sep 10, 2019

Thanks @DigBickNibba for the heads up, installing the Beta fixed it.

@asyba

This comment has been minimized.

Copy link

commented Oct 13, 2019

This is working? the 7 digit generated from Authy vs 1Password is different.

@Techinicabor

This comment has been minimized.

Copy link

commented Oct 16, 2019

"Known to work: Google Authenticator" You sure? From here it reeeeeeally looks like it doesn't.

@TauPan

This comment has been minimized.

Copy link

commented Oct 17, 2019

How does one use your code assuming no familiarity with Go

Glad to hear you got it working. You can also just download a standalone binary to avoid messing about with compilers.

Unfortunately, all the methods mentioned here fail to recover my humble bundle token for use in keepassxc. I guess I'll need to re-create it.

First thing that goes wrong: All codes here (including the go program) print the period as 10 seconds, which is clearly too little (the timer in the authy app runs around 20 seconds).

If I put in 7 digits and a value around 20 seconds (I tried everything from 15 to 25), the generated values in keepassxc are still wrong.

If I put in 10 seconds, the generated codes have no relation whatsoever to the ones displayed in the authy app.

@puddly

This comment has been minimized.

Copy link

commented Oct 17, 2019

@TauPan: The period is definitely 10s. Authy uses 10s internally but displays it for 20s so you always have time to type it. Server and client times aren't always synced up so your current code, the previous code, and even the next code (if not more) will all be accepted.

@alexzorin

This comment has been minimized.

Copy link

commented Oct 18, 2019

Indeed, the true period of "Authy apps" is 10s. What the Authy clients do is pick a token to display that is going to be valid for two periods (by abusing clock skew leeway), which is why it seems like the the period is 20s.

It's not possible to convey this proprietary piece of magic in the standard TOTP URI format, so you have to live with a 10 second period.

If I put in 10 seconds, the generated codes have no relation whatsoever to the ones displayed in the authy app.

Again, this is not unexpected. "Authy Apps" work in a dramatically different way to normal Authy TOTP tokens that are scanned by QR or UR and then stored in an encrypted format in your Authy account.

With "Authy Apps" like Twitch and Humble Bundle (formerly, they switched over to real TOTP now), each device that you register to your Authy account is handed out a different TOTP secret by the Authy backend.

That is why the e.g. the Go program generates a different TOTP token to your Authy app. They are working off different TOTP secrets, but both are valid and will authenticate you to the service.

This allows Authy to revoke the secrets of individual devices if there is a device compromise (or if you de-register the device from your Authy account), rather than revoking one TOTP secret that is shared by all of them.

The tl;dr; is that "Authy Apps" is not real TOTP, it's a superficial facade over an authn/authz system that looks like TOTP. That's why you will encounter difficulties if you try to pretend it is TOTP.

@jeno007

This comment has been minimized.

Copy link

commented Oct 19, 2019

I cannot get it to work.
I'm able to extract the QR code from the chrome extension and import it into 1P 7. 1P generates the OTP, but when I try to use it to login to Twilio"s website (with authy code input selected), I got the "The verification code did not match" error.
The code shown in the 1P and in the Authy Chrome extension didn't match either. Shouldn't they be the same? They are generated from the same secret as the secret was extracted from the chrome extension.
The code shown in the Chrome extension does work to authenticate to Twilio.

@TauPan

This comment has been minimized.

Copy link

commented Oct 21, 2019

Indeed, the true period of "Authy apps" is 10s. What the Authy clients do is pick a token to display that is going to be valid for two periods (by abusing clock skew leeway), which is why it seems like the the period is 20s.

It's not possible to convey this proprietary piece of magic in the standard TOTP URI format, so you have to live with a 10 second period.

If I put in 10 seconds, the generated codes have no relation whatsoever to the ones displayed in the authy app.

Again, this is not unexpected. "Authy Apps" work in a dramatically different way to normal Authy TOTP tokens that are scanned by QR or UR and then stored in an encrypted format in your Authy account.

With "Authy Apps" like Twitch and Humble Bundle (formerly, they switched over to real TOTP now), each device that you register to your Authy account is handed out a different TOTP secret by the Authy backend.

That is why the e.g. the Go program generates a different TOTP token to your Authy app. They are working off different TOTP secrets, but both are valid and will authenticate you to the service.

This allows Authy to revoke the secrets of individual devices if there is a device compromise (or if you de-register the device from your Authy account), rather than revoking one TOTP secret that is shared by all of them.

The tl;dr; is that "Authy Apps" is not real TOTP, it's a superficial facade over an authn/authz system that looks like TOTP. That's why you will encounter difficulties if you try to pretend it is TOTP.

Thanks for the clarification @alexzorin. I must admit I didn't actually try to authenticate to Humble Bundle with the generated codes, I just compared them to the ones in the "Authy" app for android and they were very different. I'll try with a 10 seconds period at some point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.