Skip to content

Instantly share code, notes, and snippets.

@gboudreau
Forked from Ingramz/AuthyToOtherAuthenticator.md
Last active November 17, 2024 04:56
Show Gist options
  • Save gboudreau/94bb0c11a6209c82418d01a59d958c93 to your computer and use it in GitHub Desktop.
Save gboudreau/94bb0c11a6209c82418d01a59d958c93 to your computer and use it in GitHub Desktop.
Export TOTP tokens from Authy

Exporting your 2FA tokens from Authy to transfer them into another 2FA application

IMPORTANT - Update regarding deprecation of Authy desktop apps

Past August 2024, Authy stopped supported the desktop version of their apps:
See Authy is shutting down its desktop app | The 2FA app Authy will only be available on Android and iOS starting in August for details.

And indeed, after a while, Authy changed something in their backend which now prevents the old desktop app from logging in. If you are already logged in, then you are in luck, and you can follow the instructions below to export to tokens.

If you are not logged in anymore, but can find a backup of the necessary files, then restore those files, and re-install Authy 2.2.3 following the instructions below, and it should work as expected.

If you can't go back to a "logged in" state in the desktop app (because you either never used the desktop app, or you did but don't have a backup of the necessary files), then your only option now to export your tokens is to use an Android phone, root it, and use that to access the Authy data. Look in the comments below to find instructions on how to do that:


The instructions below explain how to use the Authy desktop app to export your 2FA tokens. If that doesn't work, look for links in the section above, to find other options you can try.

--

This gist, based in part on a gist by Brian Hartvigsen, allows you to export from Authy your TOTP tokens you have stored there.
Those can be "standard" 6-digits / 30 secs tokens, or Authy's own version, the 7-digits / 10 secs tokens.

Since the Authy "desktop" app is a Chromium-based web-app, we'll use the Developer Tools provided by Chromium to execute Javascript code that will export the tokens in JSON or as QR codes. You can then import or manually add those in you preferred application.

Important: If you have any accounts that use the Authy TOTP SDK (eg. Gemini, Twitch, Sendgrid, Twilio, ...), you can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy specifically! Your only option here would be to go in those accounts, disable Authy 2FA, and enable another 2FA method. More details here.

Detailed How-To

  1. Install Authy desktop app, version 2.2.3 (the more recent versions won't work).

    Note: If you are prompted to update, do NOT do it; the latest version doesn't support --remote-debugging-port needed in point (2) below.

    (Click your OS below to get personalized instructions.)

    macOS

    Download and install this file: https://pkg.authy.com/authy/stable/2.2.3/darwin/x64/Authy%20Desktop-2.2.3.dmg
    MD5 hash: ab7e4ae5b88cb71f84394df6989950aa

    You can use the following command in Terminal, before launching Authy Desktop, to disable auto-updates:

    mkdir -p ~/Library/Caches/com.authy.authy-mac.ShipIt ; rm -rf ~/Library/Caches/com.authy.authy-mac.ShipIt/* ; chmod 500 ~/Library/Caches/com.authy.authy-mac.ShipIt
    Windows

    You can use the winget (CLI) tool:

    winget install --no-upgrade --force -e --id Twilio.Authy -v 2.2.3
    

    Or download and install one of those:
    64-bit: https://pkg.authy.com/authy/stable/2.2.3/win32/x64/Authy%20Desktop%20Setup%202.2.3.exe
    MD5 hash: efd176d89b280809b9f84fda9ba50840
    32-bit: https://pkg.authy.com/authy/stable/2.2.3/win32/x32/Authy%20Desktop%20Setup%202.2.3.exe
    MD5 hash: d66d63abb482523ad27dfe676e249fff

    Authy will start after installation. Close it ASAP.

    To prevent auto-update, go to the %LOCALAPPDATA%\authy folder, and delete Update.exe. Delete the app-2.5.0 folder, if it exists. (The version number will probably be a higher number.) In the app-2.2.3 subfolder, delete Update.exe.
    Of note: If you later want to uninstall Authy, you'll need to restore those files, as Update.exe is the executable used by the uninstallation process.

    Or, after the app updated, you can change your shortcut to execute "%LOCALAPPDATA%\authy\app-2.2.3\Authy Desktop.exe" --remote-debugging-port=5858 and change the Start in to %LOCALAPPDATA%\authy\app-2.2.3
    Even after an update is installed, 2.2.3 is still installed.

    Linux (using snap) (recommended)
    cd /tmp
    # curl -Lo authy.snap https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap
    curl -Lo authy.snap https://filebrowser.patati.ca/api/public/dl/Tk1sjeEi/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap # Copy of above file that is now gone
    if ! echo a488d3f3c06672a78f53da144f4325d8 authy.snap | md5sum -c --status ; then
        echo "Error: invalid MD5 hash"
    else
        unsquashfs -q -f -d authy-2.2.3 authy.snap
        cd authy-2.2.3/
    fi
    Linux (using flatpak) (alternative method if snap above doesn't work) (NOT WORKING ANYMORE)

    It seems flathub is using the api.snapcraft.io repo behind the scene, so trying to install using the below commands will fail, now that the Authy app was removed from api.snapcraft.io. Try to install directly the snap (using the above method), instead of using flatpak.

    flatpak install flathub com.authy.Authy
    # Update to the 2.2.3 commit (found this commit using: flatpak remote-info --log flathub com.authy.Authy)
    sudo flatpak update --commit=83c0df0dd48bbb6ad851f5cc62d6e0836e56e499c7a79041241809f8296e65cc com.authy.Authy
    # Optionally, if you want to export a JSON file, give access to Authy to your Home folders:
    sudo flatpak override --filesystem=home com.authy.Authy
  2. Start Authy desktop app, but add the --remote-debugging-port=5858 parameter to the command-line:

    macOS

    From Terminal.app: open -a "Authy Desktop" --args --remote-debugging-port=5858

    Windows

    Right-click the Authy desktop shortcut, and in the Target field write --remote-debugging-port=5858 at the end. Then click OK. Double-click the Authy desktop shortcut.

    Linux

    From a terminal: ./authy --remote-debugging-port=5858 (if you used snap)
    or flatpak run com.authy.Authy --remote-debugging-port=5858 (if you used flatpak)

  3. In Authy, Log in so you can see the codes being generated for you.

  4. If you have some codes that show a padlock next to them, you will need to enter your Backup Password before continuing below, or those codes won't be exported correctly (decryptedSeed will be empty).

  5. Open the following URL in Google Chrome (or any Chromium-based browser): http://localhost:5858

  6. Click the Twilio Authy link in that webpage.

  7. In Chrome Developer Tools top navigation bar, go in the Sources tab (if you don't see it, click >> to expand the full list), then select the Snippets sub-tab (tabs on the second line; again, click >> to expand the full list), and finally choose + New snippet.

    Careful here: do NOT open the Chrome Developer Tools like you normally do. When you go to http://localhost:5858, and click the Twilio Authy link in that webpage, it will show you Developer Tools for the Authy app. This is where you need to work. Here's a video that shows you exactly where you need to be, when you paste code: https://youtu.be/nArCf8iEqlw

  8. If you'd like to ensure the code below doesn't send anything to a remote server, you can disconnect from the internet now.

  9. In the snippet editor window that appears on the right, paste one of the following code options:

    Simplest

    This is the simplest form there is, and it will simply show you an object for each code you have in Authy. You can use that if you're scared to run complicated code you don't understand (i.e. the other options below).

    appManager.getModel().forEach(i => console.log(i))
    Simple

    This is still quite simple, but makes it easier to copy-paste everything out of the console in one operation.

    appManager.getModel().forEach(i => {
       console.log("{");
       console.log("    createdDate: " + i.createdDate);
       console.log("    accountType: " + i.accountType);
       console.log("    name: " + i.name);
       console.log("    originalName: " + i.originalName);
       console.log("    decryptedSeed: " + i.decryptedSeed);
       console.log("}");
    })
    QR codes

    This version will output QR codes that you can scan using another app, from your mobile device.
    If you uncomment the last line, you will also get a .json file that contains your tokens (name, secret & URL).

    All your Authy tokens will be displayed in the Console at the bottom; either copy-paste the TOTP URI, or scan the QR codes.

    // QRious v4.0.2 | (C) 2017 Alasdair Mercer | GPL v3 License Based on jsqrencode | (C) 2010 [email protected] | 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;"); }
    
    (function(console) { console.save = function(data, filename) { if (!data) { console.error('Console.save: No data'); return; } if (typeof data === "object") { data = JSON.stringify(data, undefined, 4) } var blob = new Blob([data], {type: 'text/json'}), e = document.createEvent('MouseEvents'), a = document.createElement('a'); a.download = filename; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl =  ['text/json', a.download, a.href].join(':'); e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); } })(console);
    
    console.clear();
    console.warn("Here's your Authy tokens:");
    var data = appManager.getModel().map(function(i) {
        var secretSeed = i.secretSeed;
        if (typeof secretSeed == 'undefined') {
            secretSeed = i.encryptedSeed;
        }
        var secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(secretSeed));
        var period = (i.digits === 7 ? 10 : 30);
        var totp_uri = `otpauth://totp/${encodeURIComponent(i.name)}?secret=${secret}&digits=${i.digits}&period=${period}`;
        var qr_size = 250;
        var qr_url = (new QRious({value: totp_uri, size: qr_size})).toDataURL();
        console.group(`${i.originalName} / ${i.name}`);
            console.log('TOTP secret:', secret);
            console.log('TOTP URI:', totp_uri);
            console_image(qr_url, qr_size);
        console.groupEnd();
        return {name: i.name, secret: secret, uri: totp_uri};
    });
    //console.save(data, 'authy_backup.json');
    Export to Bitwarden JSON - Simpler version

    From @oetiker (ref):

    [...] you will get a dump in json format which you can directly copy/paste into the bitwarden import tool. Since Authy does not contain complete login information, I would suggest to create a new folder for the import, so that you can then merge the TOTP tokens into the actual login entries.

    let x = []; 
    appManager.getModel().forEach(i => {
      if (i.decryptedSeed) {
        x.push({
          type: 1, 
          name: i.originalName ?? i.name ?? `[No Name] - Imported from Authy (${x.length})`,
          login: {username: i.name, totp: i.decryptedSeed}
        })
      }});
      console.log(JSON.stringify({ encrypted: false, items: x})
    );
    Export to Bitwarden JSON - Advanced

    This code can be used to save your tokens as a JSON file, for example to import into Bitwarden.
    It will create an Imported from Authy folder, and import your TOTP codes in there.

    // 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; }
    
    // from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid#answer-2117523
    function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }
    
    // from https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93
    function saveToFile(data, filename) { if (!data) { console.error('Console.save: No data'); return; } if (typeof data === "object") { data = JSON.stringify(data, undefined, 4) } const blob = new Blob([data], { type: 'text/json' }); const e = document.createEvent('MouseEvents'); const a = document.createElement('a'); a.download = filename; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); }
    
    function deEncrypt({ log = false, save = false }) {
        const folder = {
            id: uuidv4(),
            name: 'Imported from Authy'
        };
    
        const bw = {
            "encrypted": false,
            "folders": [
                folder
            ],
            "items": appManager.getModel().map((i) => {
                let secretSeed = i.secretSeed;
                if (typeof secretSeed == "undefined") {
                    secretSeed = i.encryptedSeed;
                }
                const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(secretSeed));
                const period = (i.digits === 7 ? 10 : 30);
    
                const [issuer, rawName] = (i.name.includes(":"))
                    ? i.name.split(":")
                    : ["", i.name];
                const name = [issuer, rawName].filter(Boolean).join(": ");
                const totp = `otpauth://totp/${name}?secret=${secret}&digits=${i.digits}&period=${period}${issuer ? '&issuer=' + issuer : ''}`;
    
                return ({
                    id: uuidv4(),
                    organizationId: null,
                    folderId: folder.id,
                    type: 1,
                    reprompt: 0,
                    name,
                    notes: null,
                    favorite: false,
                    login: {
                        username: null,
                        password: null,
                        totp
                    },
                    collectionIds: null
                });
            }),
        };
    
        if (log) console.log(JSON.stringify(bw));
        if (save) saveToFile(bw, 'authy-to-bitwarden-export.json');
    }
    
    deEncrypt({
        log: true,
        save: true,
    });
    Export to JSON format (2FSA / Raivo)

    @brenc says (ref):

    I have modified the snippet to produce a Raivo OTP format export file that can be directly imported into 2FAS Auth (and of course Raivo and others I'm sure):

    // 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;
    }
    
    // from https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93
    function saveToFile(data, filename) {
      if (!data) {
        console.error("Console.save: No data");
        return;
      }
    
      if (typeof data === "object") {
        data = JSON.stringify(data, undefined, 4);
      }
    
      const blob = new Blob([data], {
        type: "text/json",
      });
    
      const e = document.createEvent("MouseEvents");
      const a = document.createElement("a");
      a.download = filename;
      a.href = window.URL.createObjectURL(blob);
      a.dataset.downloadurl = ["text/json", a.download, a.href].join(":");
    
      e.initMouseEvent(
        "click",
        true,
        false,
        window,
        0,
        0,
        0,
        0,
        0,
        false,
        false,
        false,
        false,
        0,
        null
      );
    
      a.dispatchEvent(e);
    }
    
    const items = appManager.getModel().map((i) => {
        let secretSeed = i.secretSeed;
        if (typeof secretSeed == "undefined") {
            secretSeed = i.encryptedSeed;
        }
        const period = i.digits === 7 ? 10 : 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 {
            account: name,
            algorithm: "SHA1",
            counter: "0",
            digits: `${i.digits}`,
            iconType: "",
            iconValue: "",
            issuer: name,
            kind: "TOTP",
            pinned: "false",
            secret,
            timer: `${period}`,
        };
    });
    
    saveToFile(items, "Authy-To-Raivo-OTP-Export.json");
    Export to unencrypted JSON format (Aegis)

    @dvshkn says (ref):

    Based on the snippet by @brenc, here is a version that exports to unencrypted Aegis JSON format.
    To keep things short this version only dumps the JSON to the console instead of triggering a file download.
    In Aegis (on your mobile device) use Settings > Import & Export > Import from file and select Aegis file format to import the JSON 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));

    Getting Uncaught ReferenceError: appManager is not defined error?
    Go watch this YouTube video that shows you where you need to be, when you paste code: https://youtu.be/nArCf8iEqlw

  10. Right-click the snippet name on the navigator pane on the left (eg. Script snippet #1) , and choose Run.

Thanks

@cainv
Copy link

cainv commented Mar 10, 2023

Amazing. How do I buy you a coffee?

@WJTech
Copy link

WJTech commented Mar 17, 2023

Unfortunately did not work for me, TOTP are showing as null :(

That happens if you don't authenticate the desktop app with your backup password.

@scanmikey
Copy link

Thanks so much...Worked like a charm for me! (Authy Desktop 2.2.3 on Windows 11).

@KamilsView
Copy link

KamilsView commented Mar 27, 2023

It worked for me. I just needed to disable the Master Password. Big thanks. But I could not import into Aegis app - it threw an error.

So in Aegis I chose import from BitWarden and it worked!

Thanks
Kamil

@AlephOmicron
Copy link

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

Does this apply only to the new kind of Authy tokens (7 digits) or all of them? If I only have 6 digit codes, is it safe to delete my Authy account?

@hazrpg
Copy link

hazrpg commented Apr 13, 2023

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

Does this apply only to the new kind of Authy tokens (7 digits) or all of them? If I only have 6 digit codes, is it safe to delete my Authy account?

Works fine for both 7 digits and 6 digits - it will automatically create the URL accordingly, likewise with the QR codes too! Scan, or copy and paste the url like I did, and go!

P.S. Only found this today, love it! Thanks to all for this, and all the people who have forked it and updated it! Love you guys for doing this! Finally got all 46+ of my codes out of their stupid app!

Now I just need to figure out what 4 of my codes are for because they're unhelpfully just my username's - no indication of what site they were for, and clearly not used enough for me to remember what they were associated with. Hate it when QR codes don't add a decent description. Bitwarden all the way!

@KamilsView
Copy link

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

This sounds like a lot of bollocks. Why would that lock me out? How? Magically? Somehow other services have Authy account deletion detection mechanisms and also are programmed to lock the user out when that happens. WTF?

@simeon-walker
Copy link

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

This sounds like a lot of bollocks. Why would that lock me out? How? Magically? Somehow other services have Authy account deletion detection mechanisms and also are programmed to lock the user out when that happens. WTF?

I didn't get this either. Perhaps it just means don't delete Authy until you have confirmed that all the migrated tokens work?

@amitsax
Copy link

amitsax commented Apr 15, 2023

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

This sounds like a lot of bollocks. Why would that lock me out? How? Magically? Somehow other services have Authy account deletion detection mechanisms and also are programmed to lock the user out when that happens. WTF?

I didn't get this either. Perhaps it just means don't delete Authy until you have confirmed that all the migrated tokens work?

My understanding-
This restriction of not deleting Authy account applies to accounts that directly authentic via Authy and are not using generic TOTP algo for code generation. In those cases your Authy account is connected to the service as a device and the seed is merely mirroring that connected device. So when you delete the authy account the related original seed value becomes useless. Twillio, twitch use such linked device kind of authentication.

@RokeJulianLockhart
Copy link

Yeah, I'm quite confident that that's it.

@AlephOmicron
Copy link

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

This sounds like a lot of bollocks. Why would that lock me out? How? Magically? Somehow other services have Authy account deletion detection mechanisms and also are programmed to lock the user out when that happens. WTF?

I didn't get this either. Perhaps it just means don't delete Authy until you have confirmed that all the migrated tokens work?

My understanding- This restriction of not deleting Authy account applies to accounts that directly authentic via Authy and are not using generic TOTP algo for code generation. In those cases your Authy account is connected to the service as a device and the seed is merely mirroring that connected device. So when you delete the authy account the related original seed value becomes useless. Twillio, twitch use such linked device kind of authentication.

how do we tell which accounts this applies to, and what do we do about it?

@japzone1
Copy link

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

This sounds like a lot of bollocks. Why would that lock me out? How? Magically? Somehow other services have Authy account deletion detection mechanisms and also are programmed to lock the user out when that happens. WTF?

I didn't get this either. Perhaps it just means don't delete Authy until you have confirmed that all the migrated tokens work?

My understanding- This restriction of not deleting Authy account applies to accounts that directly authentic via Authy and are not using generic TOTP algo for code generation. In those cases your Authy account is connected to the service as a device and the seed is merely mirroring that connected device. So when you delete the authy account the related original seed value becomes useless. Twillio, twitch use such linked device kind of authentication.

how do we tell which accounts this applies to, and what do we do about it?

If you open your Authy app and go to Settings-> Accounts, you'll see a section called "Authy Accounts" listing all services using the Authy API.

Only thing you can do about it is to go to those services and see if they have any alternative options for 2FA. If the service requires Authy for 2FA, then your only option is to not delete your Aunty account in order to make sure the TOTP tokens for those specific services you exported remain valid. You can use your preferred TOTP app, but you'll have to leave your Authy account alone.

@daegalus
Copy link

Important: You can NOT delete your Authy account, even after migrating your TOTP tokens to another software! If you do, you could be locking yourself out of all the accounts that require Authy!

This sounds like a lot of bollocks. Why would that lock me out? How? Magically? Somehow other services have Authy account deletion detection mechanisms and also are programmed to lock the user out when that happens. WTF?

I didn't get this either. Perhaps it just means don't delete Authy until you have confirmed that all the migrated tokens work?

My understanding- This restriction of not deleting Authy account applies to accounts that directly authentic via Authy and are not using generic TOTP algo for code generation. In those cases your Authy account is connected to the service as a device and the seed is merely mirroring that connected device. So when you delete the authy account the related original seed value becomes useless. Twillio, twitch use such linked device kind of authentication.

how do we tell which accounts this applies to, and what do we do about it?

All the authy services use 7 digit codes and I think have 20 second windows instead of 30.

Also anyone can delete their authy account at https://authy.com/account/delete/

I even just did it and I have like 7 authy services (don't use any except Twitch, and I disabled it there in favor of SMS so I don't use authy). I migrated away to Bitwarden many years ago.

@olanystrom
Copy link

I cant get the --remote-debuging-port=5858 to work with Authy Desktop 2.3.0 on windows. It starts, but does not listen on port 5858 =(
Have they disabled that functionality?

@Grishkaone
Copy link

It starts, but does not listen on port 5858

Same here, but on Mac. Nothing on 5858.

@jeffre
Copy link

jeffre commented Apr 19, 2023

Downgraded to Authy 2.2.3 to get the port listened

@Grishkaone
Copy link

Thanks @joefitzgerald and @jeffre !
Had to play a little to prevent autoupdate but it worked 👍

@ptsgmartins
Copy link

ptsgmartins commented Apr 20, 2023

Thanks @joefitzgerald and @jeffre !
Had to play a little to prevent autoupdate but it worked 👍

@Grishkaone How can one prevent autoupdate?

@Grishkaone
Copy link

Grishkaone commented Apr 20, 2023 via email

@ptsgmartins
Copy link

ptsgmartins commented Apr 20, 2023

@Grishkaone Thanks; it works! 👍

@eugeneketeni
Copy link

eugeneketeni commented Apr 21, 2023

Works like charm. Thank you.
I had to remove the update file in the Authy Desktop file location and in the app-2.2.3 folder in the same location after installing version 2.2.3 to prevent auto-update.

@ManuzCam
Copy link

Thank you, it works

@ajshastri
Copy link

ajshastri commented Apr 26, 2023

I'm using Version 2.2.3 (2.2.3) from the link you posted.

I was able to access the remote debugging on http://localhost:5858/, but I get the following error when I run the snippet:

   Uncaught ReferenceError: appManager is not defined
        at Script snippet #1:14:12
   (anonymous) @ Script snippet #1:14

Is there a way to get around that?

Chrome version: 112.0.5615.122
Authy version: 2.2.3
OS: Windows 10 Enterprise

Thanks!

image

Edit:
I tried using Brave Version 1.50.121 Chromium: 112.0.5615.138 and I get the same result.

@automationator
Copy link

automationator commented Apr 26, 2023

This worked like a charm! The one thing I did have to tweak, though, was that I noticed the console.group(i.name); line was not outputting a meaningful name in several cases for me. A lot of them just ended up being my email address or username, so I didn't have any way to know which service they actually belonged to.

I changed that line to this:

console.group(`${i.originalName} / ${i.name}`);

And now between those two variables I'm able to match up each secret with its service.

@gboudreau
Copy link
Author

I was able to access the remote debugging on http://localhost:5858/, but I get the following error when I run the snippet:

   Uncaught ReferenceError: appManager is not defined

You are probably not using the correct Developer Console.
Try again to read the instructions carefully. There are some important details in there, so if you try to use the Developer Console like you normally do, it might not work. Using the Dev Console for a remote app is a little different.

@ajshastri
Copy link

I was able to access the remote debugging on http://localhost:5858/, but I get the following error when I run the snippet:

   Uncaught ReferenceError: appManager is not defined

You are probably not using the correct Developer Console. Try again to read the instructions carefully. There are some important details in there, so if you try to use the Developer Console like you normally do, it might not work. Using the Dev Console for a remote app is a little different.

You are right, thank you for pointing it out, I was able to get to the right console and retrieve the totp's. Thank you very much!

@maneldelgado
Copy link

Works like charm. Thank you. I had to remove the update file in the Authy Desktop file location and in the app-2.2.3 folder in the same location after installing version 2.2.3 to prevent auto-update.

This worked for me too! Nice workaround if someone has the auto-update problem.
When Authy shows the update pop-up it doesn't let you connect to localhost without even interacting with the pop-up... Seems like they don't want us to leave.

@neekt
Copy link

neekt commented Apr 27, 2023

For anyone trying to revert to 2.2.3 on Linux, you can list which versions of Authy are installed on your system by running

snap list authy --all

Which should output something like

Name   Version  Rev  Tracking       Publisher     Notes
authy  2.2.3    18   latest/stable  twilio-authy  disabled
authy  2.3.0    19   latest/stable  twilio-authy  -

If you still have 2.2.3 installed, you can revert to it by running

sudo snap revert authy --revision 18

The Authy channel on snap itself doesn't seem to allow downloading of older versions, so reverting was the only way I found to do this.

Kudos to @gboudreau for this gist.

@cstrouse
Copy link

cstrouse commented May 2, 2023

You can also go into the directory where the electron app's asar archive is located, extract it, and then run authy from there using npx which works with --remote-debugging-port=5858 even though they've set dflag=false in the manifest to disable debugging, added asar integrity enforcement, and signed the binaries (hardened targets can't be debugged).

$ cd /Applications/Authy\ Desktop.app/Contents/Resources
$ npx asar extract app.asar /tmp/authy-src
$ cd /tmp/authy-src
$ npx electron . --remote-debugging-port=5858
Need to install the following packages:
  [email protected]
Ok to proceed? (y) y

DevTools listening on ws://127.0.0.1:5858/devtools/browser/baad4677-3889-4d33-ba31-007d3888798c

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