Skip to content

Instantly share code, notes, and snippets.

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

Generating Authy passwords on other authenticators


*update: TBC, but this new might affect how easy it is to use this technique past August 2024: Authy is shutting down its desktop app | The 2FA app Authy will only be available on Android and iOS starting in August


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 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;"); }
    
    (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

@fizzfaldt
Copy link

fizzfaldt commented Feb 14, 2024

If you're (wisely) concerned about running a minified chunk of code copied and pasted from an untrusted source against your sensitive data, read this.

TL;DR

This is all you need to run in the Chrome console:

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

Details

My system:

* Ubuntu 22.04

* Authy 2.4.1 installed via Snap store

I had a few versions installed from the Snap store, so I used the oldest one I had installed. Here's exactly what I ran:

mkdir -p /tmp/authy
npx asar extract /snap/authy/current/resources/20/resources/app.asar /tmp/authy
cd /tmp/authy
npx electron . --remote-debugging-port=5858 --remote-allow-origins=http://localhost:5858

Then open Chrome to localhost:5858 and click Twilio Authy.

Less scary code

Run this into the Chrome console:

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

This will print all of the info you came here for. Everything else you can do on your own. (Tip: Right-click on each of the collapsed objects and click "Copy object")

Importing into 1Password

Paste the decryptedSeed value into the "one-time password" field in 1Password. You don't need the QR code.

I used this ^^^ but changed script to

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("}");
})

to make it easy to copy/paste everything instead of one at a time.
(I kept only the fields I cared about)

This worked without having to downgrade to 2.2.3 or anything. (I did have to sign in again though)

@bengalih
Copy link

Wanted to also just give thanks. Was a bit finicky, but the guidance in the comments helped. Will make moving off Authy much easier.

@elliotwutingfeng
Copy link

@zampoteh
Copy link

I see that the generated QR codes as well as all types of generated JSONs (Bitwarden / Ravio / Aegis) are missing icon data. So when I import them to 2FAS it uses default icons for tokens rather than the icons that I had in Authy.
For instance, for GiHub token instead of the Octocat logo I'm getting a standard icon with GI letters.
Is it possible to fix this?

@joshuamkite
Copy link

@zampoteh

I see that the generated QR codes as well as all types of generated JSONs (Bitwarden / Ravio / Aegis) are missing icon data. So when I import them to 2FAS it uses default icons for tokens rather than the icons that I had in Authy. For instance, for GiHub token instead of the Octocat logo I'm getting a standard icon with GI letters. Is it possible to fix this?

In 2fas:

  • Press and hold on token icon
  • 'Edit'
  • 'Personalization'
  • Select left icon (2fas icon)
  • 'Change branding'

@phileastv
Copy link

phileastv commented Feb 14, 2024

The script in the original post to export an JSON imports flawlessly in ente.io/auth !

@zampoteh
Copy link

Thank you @joshuamkite

@edouard-lopez
Copy link

Thanks you all! 🎉

The Aegis import didn't work for me (done on Ubuntu 23.10 Linux, and Android), I had to use @brenc to export in Raivo OTP format.

If you come from Authy using the Desktop+Mobile, you might be interested in this issue, asking for a Desktop version of 2FAs

@EricoCartmanez
Copy link

EricoCartmanez commented Feb 14, 2024

On Ubuntu 22 I get:

Uncaught ReferenceError: appManager is not defined
    at deEncrypt (Script snippet %237:21)
    at Script snippet %237:58

@bengalih
Copy link

On Ubuntu 22 I get:

Uncaught ReferenceError: appManager is not defined
    at deEncrypt (Script snippet %237:21)
    at Script snippet %237:58

I was getting similar errors until I followed the advice of another poster and ran this in a browser instance with no other tabs. Close down all your tabs then open up two separate instances. One with the GH instructions and one to open the connection to Authy and run the scripts and see if it works for you.

@EricoCartmanez
Copy link

On Ubuntu 22 I get:

Uncaught ReferenceError: appManager is not defined
    at deEncrypt (Script snippet %237:21)
    at Script snippet %237:58

I was getting similar errors until I followed the advice of another poster and ran this in a browser instance with no other tabs. Close down all your tabs then open up two separate instances. One with the GH instructions and one to open the connection to Authy and run the scripts and see if it works for you.

I had to reboot my laptop, opened authy again and it asked me to log in. After that it worked.

@EricoCartmanez
Copy link

If you want the Linux 2.2.3 version you can also find it here:

https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap

You do not even need to install it as a snap. Just extract the contents like so

unsquashfs -q -f -d authy-2.2.3 H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap

and launch it

authy-2.2.3/authy --remote-debugging-port=5858 &

When you have done your thing, just delete the snap package and the authy-2.2.3 directory.

That's the best way to do it on Ubuntu.

@adonetgithub
Copy link

adonetgithub commented Feb 14, 2024

I can confirm that it works on Linux Mint Debian edition.

I added snap and installed the authy snap package using sudo snap install H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap from a terminal in the folder where the snap package was downloaded, after downloading the snap package mentioned above by @EricoCartmanez.

Then I used sudo snap refresh --hold= authy to prevent it from being updated automatically.
And snap list to check if it is held from auto updates.
Then follow the manual from the beginning starting from authy --remote-debugging-port=5858 and continue from point 4.

I could enter the codes in KeepassXC. I could scan the QR codes in Aegis on my Android, and export them to Google Authenticator on my iPhone.

Thanks very very much.

@testsnake
Copy link

Absolute legend! Thought my OpenAI auth would be stuck on Authy forever.

@oetiker
Copy link

oetiker commented Feb 15, 2024

So after reading all your comments, I finally got everything working. Thanks! I have condensed the recipe I used here: Migrating Authy to Bitwarden (Linux Edition)

@adonetgithub
Copy link

Question: using unsquashfs -q -f -d authy-2.2.3 H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap is the way to run a snap without installing it, or is it another way to install it?

@gboudreau
Copy link
Author

Question: using unsquashfs -q -f -d authy-2.2.3 H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap is the way to run a snap without installing it, or is it another way to install it?

It just extracts the .snap file into a folder (a new authy-2.2.3 subfolder, in the current folder). So it doesn't install it where it would normally install it. You just cd authy-2.2.3, and you can then run ./authy from there.

(I updated the original Gist to provide those steps.)

@1icolo
Copy link

1icolo commented Feb 15, 2024

For anyone having a problem with Snap, just use the windows installer and wine. just add --remote-debugging-port=5858 at the end of the command.
wine /path/to/authy/exe --remote-debugging-port=5858

@mjmeli
Copy link

mjmeli commented Feb 15, 2024

I had trouble on Windows accessing http://localhost:5858, despite trying a few different things (e.g. disabling firewalls). I managed to get it to work on a separate Windows machine using the same process. I'm not sure what was different as they were generally configured the same. The main difference is that I had never had Authy version > 2.2.3 installed on the 2nd machine, while on the original machine I had uninstalled to do a downgrade.

Anyway, if anyone is running into similar issues, I'd suggest trying a different machine if possible or just spinning up a Linux VM.

@MJSPollard
Copy link

Thank you

@N3orun
Copy link

N3orun commented Feb 15, 2024

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("}");
})

decrypted seed is always: undefined - for me. Any ideas?

@gboudreau
Copy link
Author

decrypted seed is always: undefined - for me. Any ideas?

Did you decrypt your codes? Authy encrypts them, and you sometimes need to enter a (backup) password to decrypt them.

@N3orun
Copy link

N3orun commented Feb 15, 2024

Did you decrypt your codes? Authy encrypts them, and you sometimes need to enter a (backup) password to decrypt them.

Ah thanks, I wasn't aware that the desktop app had to be decrypted first.

@SleepyPeko
Copy link

Tysm, exported Authy to json and imported to Aegis and it worked!!

@Happygolucky254
Copy link

I had trouble on Windows accessing http://localhost:5858, despite trying a few different things (e.g. disabling firewalls). I managed to get it to work on a separate Windows machine using the same process. I'm not sure what was different as they were generally configured the same. The main difference is that I had never had Authy version > 2.2.3 installed on the 2nd machine, while on the original machine I had uninstalled to do a downgrade.

Anyway, if anyone is running into similar issues, I'd suggest trying a different machine if possible or just spinning up a Linux VM.

Some browsers block insecure content on http only links like chrome but they don't say so when it fails, just connection refused or something, try site setting and change it temporarily.

@danielRuoss
Copy link

Im running the authy desktop on win 11 and want to mirgrate to 1password
I downgraded to v2.2.3 and blocked the updates
i follow all the steps
and im running the instuctions in firefox and the devtools in chrome
Still getting the VM104 Script snippet #2:1 Uncaught ReferenceError: appManager is not defined
error
any suggestions

@x3rt
Copy link

x3rt commented Feb 16, 2024

Just wanted to say thank you!
I hated Authy for years, but thought all my TOTP were stuck in Authy forever so I just dealt with it.
Imported them into Bitwarden - no hassle or any issues

@Tommi2Day
Copy link

I found this authy-migration tool very handy to export the authy secrets with a single binary. No hassle with auty versions, it registers itself as device and exports all the secrets at once as text or html (qr with secrets). Of course, you should deregister the new device afterwards

@ccombe
Copy link

ccombe commented Feb 18, 2024

incredible stuff, I was able to at least export to JSON which is amazing in and of itself and import into bitwarden despite running into a couple failed to fetch snags but I think that was more an issue with having the desktop app and the chrome add-in competing for attention ;)

@gianpaj
Copy link

gianpaj commented Feb 18, 2024

FYI: Don't open the http://localhost:5858/ (Authy devtools) on Firefox. While it works. You can't expand the outputs or copy and paste the values.
Use Google Chrome. I tested this on macOS

@MatthiasNZ
Copy link

MatthiasNZ commented Feb 19, 2024

When I click the Twilio Authy link in the browser (Brave, a Chrome branch, I believe), I get the spinning "busy" icon and nothing happens. After pasting and running the 1st or 2nd script I get the Uncaught ReferenceError: appManager is not defined, like other posters reported. Any ideas?

Have temporarily disabled my virus checker, to no avail. Do I have to fiddle with the firewall?

@x3rt
Copy link

x3rt commented Feb 19, 2024

Do I have to fiddle with the firewall?

No you shouldn't have to touch your AV or Firewall for this to work.
Try what other people said fixed the same issue (Restart your computer, and use a different browser with only one tab open)

@gboudreau
Copy link
Author

When I click the Twilio Authy link in the browser (Brave, a Chrome branch, I believe), I get the spinning "busy" icon and nothing happens. After pasting and running the 1st or 2nd script I get the Uncaught ReferenceError: appManager is not defined, like other posters reported. Any ideas?

Where are you pasting the code? If you can't reach Authy's dev tools, you won't be able to run the code in the right context, and thus you won't be able to access Authy's codebase & data (appManager is only available in Authy's code). You need to use the proper dev tools, which are only available when you click the Twilio Authy link on http://localhost:5858/
That step is not optional.

If your browser has issues opening that link, try another browser. Chrome should work.

@gboudreau
Copy link
Author

@MatthiasNZ I created a video that shows you where you need to be, to use any of the code provided above: https://youtu.be/nArCf8iEqlw

@MatthiasNZ
Copy link

@gboudreau & @x3rt thanks, after reboot and Chrome with just 2 tabs open (in addition to localhost I need access to this page for code & instructions) it worked.

Didn't have all the earlier comments open that may have contained these suggestions.

@MatthiasNZ
Copy link

General question: since folk mention Bitwarden and 1Password as alternative 2FA apps, how safe is using a pw manager for storing passwords, tokens and use it as a TOTP generator? While convenient, doesn't that defeat the idea of 2FA?

@brenc
Copy link

brenc commented Feb 19, 2024

@MatthiasNZ yes this is why I keep my password storage, 2fa, and 2fa backup codes all separate.

@magnetikonline
Copy link

Instructions worked a treat - using the Export to unencrypted JSON format (Aegis) method produced keys that played perfectly with KeePassXC 👍

@Goorzhel
Copy link

@gianpaj:

FYI: Don't open the http://localhost:5858/ (Authy devtools) on Firefox. You can't expand the outputs or copy and paste the values.

Au contraire, copy-paste works fine in Firefox (for Windows). I used the instructions in the "Simple" header.

@3raxton
Copy link

3raxton commented Feb 20, 2024

Hello, I have got a question regarding the statement
"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!"
As far as I understand a token is not related in any way to the App provider. If I transfer the token from Authy to Google Authenticator and delete my Authy account, why should this be a problem?

It's for services that instead of using standard TOTP, use Authy's API to link the 2FA from their site to Authy. Twitch for example. So if you delete your Authy account, it invalidates the key between Authy and that Service, and the 2FA no longer works.

Exactly. I THINK you can identify those services by the number of digits in the TOTP codes: Authy codes have 7 digits, while regular (non-Authy) codes have 6 digits.

If a service you have setup 2FA with uses the Authy API/SDK, it doesn't even know the secret needed to validate the codes generated by the TOTP token, nor your phone number; it only knows your Authy user ID, and if you delete that Authy account, that service will not be able to validate 2FA codes that would be valid, and thus you can be locked out of that service. What you need to do, if you really want to close your Authy account, is to make sure the providers you had in Authy are NOT advertising Authy for 2FA, but just plain TOTP. If you find one that does say it uses Authy, you should, for that service, disable 2FA, and re-enable it WITHOUT choosing Authy as a 2FA method. If they offer plain TOTP, choose that, and use whatever new TOTP app (that is NOT Authy) to set up 2FA there.

That warning was added because it happened to me when I initially migrated out of Authy: TOTP tokens were migrated to 1Password, and I tested that the codes generated were valid, and that I could use them to log in, but as soon as I deleted my Authy account, they stopped working because the service provider (Cloudflare) used the Authy SDK. (Cloudflare does NOT use the Authy SDK anymore.)

Sendgrid & Twilio are other examples (apart from the already mentioned Twitch) that offer Authy 2FA (Authy being owned by Twilio...)

Am I correctly understanding that I can remove the 2FA from the parities using Authy's API (Cloudflare, Pinterest, Braze), with the exclusion of Gemini, and move them to a new 2FA provider without losing access to these accounts? I've done this with Clouldfare and haven't had any issues thus far.

As long as I do not delete my Authy account, things should work just fine. Is there anything that I am missing?

@gboudreau
Copy link
Author

Am I correctly understanding that I can remove the 2FA from the parities using Authy's API (Cloudflare, Pinterest, Braze), with the exclusion of Gemini, and move them to a new 2FA provider without losing access to these accounts? I've done this with Clouldfare and haven't had any issues thus far.
As long as I do not delete my Authy account, things should work just fine. Is there anything that I am missing?

Yes. For any service that advertise they are using Authy, you go into the service settings, disable 2FA, and then re-enable 2FA but do NOT use Authy.

@3raxton
Copy link

3raxton commented Feb 20, 2024

Am I correctly understanding that I can remove the 2FA from the parities using Authy's API (Cloudflare, Pinterest, Braze), with the exclusion of Gemini, and move them to a new 2FA provider without losing access to these accounts? I've done this with Clouldfare and haven't had any issues thus far.
As long as I do not delete my Authy account, things should work just fine. Is there anything that I am missing?

Yes. For any service that advertise they are using Authy, you go into the service settings, disable 2FA, and then re-enable 2FA but do NOT use Authy.

Thank you for sharing this with me. After re-enabling using a different 2FA service, can they be deleted from Authy or do they need to stick around to avoid issues with the API down the line?

I'm thinking that ideally, with a backup code, the deletion wouldn't become an issue down the line as long recovery codes stick around but I'm not quite sure.

@3raxton
Copy link

3raxton commented Feb 20, 2024

I'm running into the invalid MD5 hash on my Mac. I've downloaded the 2.2.3 from the link provided and am running the necessary commands in terminal. Is there anything I need to do to add the MD5 hash to the DMG? I haven't yet given flatpak a shot; I've looked at ways to install it on Mac but I don't follow.

Any guidance is helpful. Thank you!

@xstable
Copy link

xstable commented Feb 20, 2024

❤️ thanks a lot for your effort an sharing it!

@xstable
Copy link

xstable commented Feb 20, 2024

Instructions worked a treat - using the Export to unencrypted JSON format (Aegis) method produced keys that played perfectly with KeePassXC 👍

Can you tell, how does the import on KeepassXC will work?

@adonetgithub
Copy link

adonetgithub commented Feb 20, 2024

I entered the secrets that you can find in the JSON file in the TOTP field in keepasXC on my Linux Mint machine. These values entered in Keepass give the app the needed information to generate the tokens. It only works with 6 digits tokens. If you use the same kbdx database on your android phone in the keepass2Android app, you cas see the TOTP tokens there as well.
I had to enter these secrets manually though.

@bradbaas2
Copy link

That worked for me once I started using KeepassXC. You have to go to the Entries tab in KeepassXC or double-click the Clock column to add it.

Windows: 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

And I just renamed Update.exe to Update.exe.old once I installed the new version. I had updated the app without realizing it would break the export, but it worked using version 2.2.3!

@3raxton
Copy link

3raxton commented Feb 21, 2024

Bumping this again to understand the MD5 hash error. Would you mind sharing with me what I can do to prevent the invalid MD5 hash error? I've added an MD5 hash to Authy 2.2.3 but I am still running into this issue. I appreciate the help, thank you.

@gboudreau
Copy link
Author

Bumping this again to understand the MD5 hash error. Would you mind sharing with me what I can do to prevent the invalid MD5 hash error? I've added an MD5 hash to Authy 2.2.3 but I am still running into this issue. I appreciate the help, thank you.

Why are you executing the Linux instructions on Mac?
The code you mention (that verifies the MD5 hash) is under Linux (using snap) (recommended). You should not run this code on Mac.

On Mac, you install the DMG, run the command to disable auto-update on Mac, and then continue with steps 2+.

@MrtnKk
Copy link

MrtnKk commented Feb 21, 2024

On my old PC I was running the snap version (2.5.0) on Ubuntu. But I switched to a new PC and Manjaro (Arch-based), so I tried the flatpak way. I can install version 2.2.3 successfully but as soon as I try to provide my phone number during setup, Authy always says, "Twilio Authy doesn't accept any new registrations". I'm using the same phone number as before and the Backup feature is enabled. Version 2.5.0 is running on my old Ubuntu machine to this day.

I also tried reverting the snap on my old PC but only version 2.5.0 is installed. The commands for snap don't seem to work as well. If I curl -o authy.snap https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap, I get an empty 'authy.snap' file.

I copied the lines for the snap solution into an empty file and made it executable but it throws an MD5 hash error (if I remember correctly).

Anything else I can try?

EDIT

Btw. I might get something wrong, but running the desktop app after installing by typing ./authy --remote-debugging-port=5858 doesn't work when I installed via flatpak. I had to flatpak run com.authy.Authy --remote-debugging-port=5858.

@3raxton
Copy link

3raxton commented Feb 21, 2024

Bumping this again to understand the MD5 hash error. Would you mind sharing with me what I can do to prevent the invalid MD5 hash error? I've added an MD5 hash to Authy 2.2.3 but I am still running into this issue. I appreciate the help, thank you.

Why are you executing the Linux instructions on Mac? The code you mention (that verifies the MD5 hash) is under Linux (using snap) (recommended). You should not run this code on Mac.

On Mac, you install the DMG, run the command to disable auto-update on Mac, and then continue with steps 2+.

I appreciate the clarity. I mixed up Linux with Unix, my apologies. I’ve got this up and running based on the instructions. After moving to a different 2FA system, is it safe to remove these codes from Authy if they don't utilize the API? Thank you

@gboudreau
Copy link
Author

If I curl -o authy.snap https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap, I get an empty 'authy.snap' file.

Sorry, that command was missing a -L:

curl -Lo authy.snap https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap

@gboudreau
Copy link
Author

After moving to a different 2FA system, is it safe to remove these codes from Authy if they don't utilize the API?

If you can obtain TOTP code from another system, to log in on a service, and that service is not using the Authy SDK, then yes, you can remove it from Authy.

@3raxton
Copy link

3raxton commented Feb 21, 2024

After moving to a different 2FA system, is it safe to remove these codes from Authy if they don't utilize the API?

If you can obtain TOTP code from another system, to log in on a service, and that service is not using the Authy SDK, then yes, you can remove it from Authy.

I am very grateful for your support, code and guidance, this is an incredible tool to utilize. Thank you for the time you've taken to put this together and help everyone out 🚀

@3oogie
Copy link

3oogie commented Feb 22, 2024

no matter what i do (win 10 or 11 desktop) these *unts force an autoupdate to 2.5 blocking the localhost access.
Spent an hour or more trying now and looks like i'll have to bite the bullet and do 50 odd totps one by one.

Unless any other ideas....

F*** YOU TWILIO.

Good riddance

Edit: also people be careful with this method as i am reading a few folks getting permalocked out of their authy accounts as theyre flagged as having hacked their own accounts.
ABSOLUTE C words.
Grrrr
Hope they die a slow death (without desktop they will do sooner than later).

@gboudreau
Copy link
Author

no matter what i do (win 10 or 11 desktop) these *unts force an autoupdate to 2.5 blocking the localhost access. Spent an hour or more trying now and looks like i'll have to bite the bullet and do 50 odd totps one by one.

Unless any other ideas...

You tried both suggestions, "To prevent auto-update [...]" and "Or, after the app updated [...]"

Maybe try to disconnect from the internet when you install and first start the app? Maybe that would prevent auto-update, then reconnect to login in the app, and disconnect again as soon as you are logged in. (Not sure if you need to be connected to the internet to enter your backup password, if any.)

If all else fails, you could just install a default Ubuntu desktop in a virtual machine (VirtualBox makes that easy), and use the snap method. Surely that would be faster than going through many accounts to re-setup 2FA.

@adonetgithub
Copy link

3oogie,

Please try the following. Install Authy using SNAP on a Linux virtual machine. Use Ubuntu if you don't know how to install snap, because some distos don't have snap installed by default. It may work in a live CD as well, but I didn't test.

If you want the Linux 2.2.3 version of Authy you can also find it here:

https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap

I installed the authy snap package using:
sudo snap install H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap
from a terminal in the folder where the snap package was downloaded, after downloading the snap package mentioned above by @EricoCartmanez. (Use file browser to open a terminal in the right folder)

Then I used
sudo snap refresh --hold= authy
to prevent it from being updated automatically.
And
snap list
to check if it is held from auto updates.

Now login into Authy, decrypt your entries. Aftehr this you may want to disconnect the linux machine from the internet.

Then follow the manual from the beginning starting from authy --remote-debugging-port=5858 and continue from point 4.
From a terminal:./authy --remote-debugging-port=5858
and continue from point 4 of the manual at the beginning of this post.

I could enter the codes in KeepassXC. I could scan the QR codes in Aegis on my Android, and export them to Google Authenticator on my iPhone. The codes generated are all the same in every app.

@3oogie
Copy link

3oogie commented Feb 22, 2024

thank you both for your help here.
eventually managed it by deleting the backup.exe from the folder stopping the program from calling back and autoupdating and then going through the process.
What was great was the few i had named NULL were where there had not been a name assigned (for eg dropbox, ring.com in my case(s)) but i rectified these while double checking them against the extant ones in authy (where they strangely had the icons despite the 'null' name - which i recognised).
THANKS very much for this work @gboudreau AND all contributors.

ONLY gripe with Aegis is the lack of icons but i have transferred tokens to 2FAS as well (which does) and will compare usage going forwards.

🫡

@Kinuseka
Copy link

@3oogie said:
ONLY gripe with Aegis is the lack of icons but i have transferred tokens to 2FAS as well (which does) and will compare usage going forwards.

Aegis does have icons. U need to download their icon pack first and import it.

@MrtnKk
Copy link

MrtnKk commented Feb 22, 2024

I also wanted to thank @gboudreau and everyone involved for the information gathered in this gist. 👍

I was finally able to backup all my Authy secrets & QR-Codes once I added the missing -L switch to the URL:
curl -Lo authy.snap https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap

The error with Authy not accepting new registrations was because of a wrong (twisted) phone number. Once I added the right one everything went smoothly. 😅

@3oogie
Copy link

3oogie commented Feb 22, 2024

@3oogie said:
ONLY gripe with Aegis is the lack of icons but i have transferred tokens to 2FAS as well (which does) and will compare usage going forwards.

Aegis does have icons. U need to download their icon pack first and import it.

not natively.
And you have to select each individual icon.

i should have worded my point better.

@Kinuseka
Copy link

no matter what i do (win 10 or 11 desktop) these *unts force an autoupdate to 2.5 blocking the localhost access. Spent an hour or more trying now and looks like i'll have to bite the bullet and do 50 odd totps one by one.

Unless any other ideas....

F*** YOU TWILIO.

Good riddance

Edit: also people be careful with this method as i am reading a few folks getting permalocked out of their authy accounts as theyre flagged as having hacked their own accounts. ABSOLUTE C words. Grrrr Hope they die a slow death (without desktop they will do sooner than later).

You can just delete the update.exe on the installation directory of authy.

I did this trick when it kept auto updating me.

@lotustarot
Copy link

lotustarot commented Feb 23, 2024

Thanks for doing this. Not too techie but what a joy when I finally crossed the finish line :)

@threehappypenguins
Copy link

I am using this method on Ubuntu to extract the raw code for a specific service that has forced me to use Authy by my phone number. Everything seemed to work fine with the snippet, but this particular one has 7 digits and a period of 20. I tried various URI constructs in both Bitwarden and Google Authenticator, and none of them will generate codes that match Authy. I also tried the qr code method, and it generated a URI like so: otpauth://totp/QWERTY%20NameOfService?secret=A1B2C3D4E5F6G7H8I9J0&digits=7&period=10

This didn't work either. I tried changing the period to 20 (I don't know why it says 10), and that also doesn't work. I tried scanning the qr code as well, and it also doesn't work. It's like it's generating the wrong secret or something. I don't know. It's also the only one in Authy without the lock, and in Authy settings, it's the only one that's listed under "Authy Accounts" and "Authenticator Accounts".

@kupietools
Copy link

kupietools commented Feb 24, 2024

Since you can't save the QR code images, if you add console.log('qrcode_dataURL',qr_url); to the console logging lines before console_image(qr_url, qr_size);, it'll save a data URI that you can save in the log file and use to regenerate the qrcode images.

For instance, if it gives you qrcode_dataURL data:image/png;base64,blahblahblahblahblahblah, you can either paste data:image/png;base64,blahblahblahblahblahblah in the browser address bar as-is to see the QR code, or, in an HTML file as <img src="data:image/png;base64,blahblahblahblahblahblah"> to create an HTML file you can open in a browser to display the QR code.

This way you can save the QR codes for later without needing to run this script again to regenerate them.

@threehappypenguins :

in Authy settings, it's the only one that's listed under "Authy Accounts" and "Authenticator Accounts".

Just a guess, but that was probably created with Authy's API that some sites use (like Gemini, Twitch, etc.) You will probably have to log in to that website, turn off Authy-specific 2FA, and turn on normal 2FA to create a new token to put into your new authenticator. You may actually lose access to those codes if you eventually delete your Authy account. See comments in https://www.reddit.com/r/Bitwarden/comments/116kpvf/export_authy_totp_to_enter_in_another_app/.

@threehappypenguins
Copy link

Just a guess, but that was probably created with Authy's API that some sites use (like Gemini, Twitch, etc.) You will probably have to log in to that website, turn off Authy-specific 2FA, and turn on normal 2FA to create a new token to put into your new authenticator. You may actually lose access to those codes if you eventually delete your Authy account. See comments in https://www.reddit.com/r/Bitwarden/comments/116kpvf/export_authy_totp_to_enter_in_another_app/.

The issue is that the site that I wanted to enable TOTP authentication on forces me to use Authy. It doesn't show me a QR code, and it doesn't give me a secret. It already has my phone number, and then it sends it to authy based on that phone number where I accept it from the Authy app. So I thought I could somehow use this app to extract the secret for the one site, and then enter that secret into Bitwarden/Vaultwarden. But it's simply not working. I tested this app with other sites, and it extracts the secrets accurately.

@BobbyWibowo
Copy link

BobbyWibowo commented Feb 24, 2024

Thank you so much! And to @brenc too for the Raivo export codes, since I want to use 2FAS instead. Everything seem to be working as I hoped so far!

Real "funny" that they removed the ability to do this on the latest update of the desktop client. At least they weren't desperate enough to disable login on outdated desktop clients.

@gregsadetsky
Copy link

gregsadetsky commented Feb 25, 2024

Thank you so so much for documenting all of this!

I wanted to export my tokens from Authy and re-import them into KeePassXC. I haven't seen anyone describe how to do this, so I wrote the missing code, and documented how to do this export + import below. Hopefully it can be of use to someone else!


First, you need to follow all of the original instructions until step 8. i.e. you need Authy version 2.2.3, you need to start it with the proper remote debugging flag, and you need to be able to connect to the Authy DevTools from Chrome.

To be sure you're at the right step, run the appManager.getModel().forEach(i => console.log(i)) snippet and check that you get some output for all of your accounts. if that's the case, you're ready to proceed.


  1. for the KeePassXC compatible CSV export, please use the following snippet

    (click to reveal)
    // 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; }
    
    // output column names
    out = ['"Group","Title","Username","Password","URL","Notes","TOTP","Icon","Last Modified","Created"'];
    
    appManager.getModel().forEach(account => {
        let title = account.name;
        if(account.accountType !== 'authenticator') {
            // .accountType can have some extra information to help
            // remember which authy account this was.
            // remove any `authenticator_` prefix
            let accountType = account.accountType.replace(/authenticator_/,'')
            title = `${title} (${accountType})`;
        }
    
        // taken from the qr code export example
        let secretSeed = account.secretSeed;
        if (typeof secretSeed == 'undefined') {
            secretSeed = account.encryptedSeed;
        }
        let secret = (account.markedForDeletion === false ? account.decryptedSeed : hex_to_b32(secretSeed));
        let period = (account.digits === 7 ? 10 : 30);
        let totp_uri = `otpauth://totp/${encodeURIComponent(account.name)}?secret=${secret}&digits=${account.digits}&period=${period}`;
    
        // generate ISO date for keepassxc import
        const created = new Date(account.createdDate).toISOString();
    
        out.push(`"Root","${title}","","","","","${totp_uri}","","${created}","${created}"`);
    })
    
    copy(out.join('\n'));
    console.log('CSV output copied to clipboard!');
  2. after running the snippet in the DevTools, the CSV output should have been copied to your clipboard

  3. go to a plain text editor (i.e. something that lets you save .txt files), paste the CSV output, and save the file as authy-export-totp.csv for example

  4. open KeePassXC. in the Database menu, choose Import > CSV File... and select the .csv file you just saved.

  5. give the new KeePassXC database a name (it doesn't really matter since this is a temporary database into which you'll be importing the tokens, and then moving them out of there). on the next page, accept the Encryption Settings.

  6. set a password for this new database (again, it doesn't really matter if you intend to bring the TOTP tokens into your "real", main database)

  7. in the CSV import settings, it's a bit hard to see, but find the "First line has field names" checkbox. check it. (it appears under the second "Encoding" column)

  8. press OK. you should finally see all the imported tokens! try them out - the TOTP value for any account should match the TOTP you get in Authy (cmd/ctrl-T on an account to get the token)

  9. moving the TOTP tokens into your main (daily) KeePassXC is a little wonky, but here's how to do it:

  10. make sure that your main database is currently open and appears in KeePassXC as a tab, next to the currently open database (the one into which you just imported the TOTP accounts)

  11. select all of the TOTP accounts you just imported in the temporary KeePassXC database (cmd/ctrl-A)

  12. drag them over to the main database tab (don't let go of the mouse yet!)

  13. the main database should "open" as you're dragging the accounts. keep dragging those accounts and drop them onto a folder (in the left sidebar). the accounts should now be transferred from the temporary database to your main one! you're done!

@tilsgee
Copy link

tilsgee commented Feb 26, 2024

image

Yo. is this error code are expected?

@thevolcanomanishere
Copy link

Thanks for this. Worked great for importing into Bitwarden

@natsukirei
Copy link

natsukirei commented Feb 28, 2024

nevermind im an idiot and didnt see the keepass instructions above

@gregsadetsky
Copy link

@natsukirei :-) hope it works for you, let me know if I can help!

@froesccn
Copy link

froesccn commented Feb 28, 2024

Thanks for this it worked for me to successfully migrate from Authy Desktop to WinAuth. My original plan was to try to "downgrade" a system with an already existing authy installation to avoid the need to connect to the internet in the first place but it turned out not be necessary since I still had a 2.2.2 running on the old laptop so I was able to do everything offline.

Also since the developer tools run on the server side you don't need Chrome I was able to do it in Firefox. I used the simple script to copy the secret codes and used t2opt for testing and was able to generate correct codes for all immediately except the Amazon one - there was something special about it with authenticator_type = 'amazon' instead of 'authenticator' and the decrypted secret was longer (sha256?). After some hours of fiddling and research I noticed the secret actually worked out of the box when added to WinAuth so huh...

@dhyanKaro
Copy link

I have the same problem as threehappypenguins had. Gemini's Authy entry export simply doesn't match up to what it should be.

@scottfoster
Copy link

Works great! Thank you so much.

@nitincodery
Copy link

In bitwarden simple script, if there's no originalName, then its not exporting it, in my case, it was only exporting single entry, hence the updated version should be:

let x = []; 
appManager.getModel().forEach(i => {
  if (i.decryptedSeed) {
    x.push({
      type: 1, 
      name: i.originalName,
      login: {username: i.name, totp: i.decryptedSeed}
    })
  }});
  console.log(JSON.stringify({ encrypted: false, items: x})
);

@encryptix
Copy link

encryptix commented Mar 2, 2024

In the output, the secret was undefined for me. I had to change the line below to always do let secret = hex_to_b32(secretSeed));

let secret = account.markedForDeletion === false ? account.decryptedSeed : hex_to_b32(secretSeed));

decryptedSeed was undefined for all my entries.

I missed the step 3

@raf-42
Copy link

raf-42 commented Mar 2, 2024

The Advanced BitWarden import worked flawlessly. Only challenge was trying to downgrade - took me a few tries until I saw a comment here pointing out that when it updates, it keeps 2.2.3 installed in a separate folder, so was just able to work from there.

Thank you so much! I was able to get my stuff migrated over and will be ditching Authy for good now.

@gboudreau
Copy link
Author

@raf-42 What do you mean, a "direct Authy link to the 2.2.3 desktop version"?
In the point (1) of the post, I give detailed per-OS instructions, including links to download the 2.2.3 version.

@gboudreau
Copy link
Author

decryptedSeed was undefined for all my entries.

Are you sure you entered your backup password, before trying to export? (See point 3.)

@raf-42
Copy link

raf-42 commented Mar 2, 2024

@raf-42 What do you mean, a "direct Authy link to the 2.2.3 desktop version"? In the point (1) of the post, I give detailed per-OS instructions, including links to download the 2.2.3 version.

....I suck at reading comprehension clearly, totally missed the dropdown. Ignore me :)

@encryptix
Copy link

encryptix commented Mar 2, 2024

decryptedSeed was undefined for all my entries.

Are you sure you entered your backup password, before trying to export? (See point 3.)

Thats it, I didnt do the 2nd line of step 3. Thanks again for the steps, very useful.

@ascandroli
Copy link

Since I don't know yet which app I'm going to use as a replacement I used this line to save a copy of the original data. I will work on the conversion using a different language using my preferred IDE.

console.log(JSON.stringify( appManager.getModel())

I'm surprised that this was not mentioned before. Is there anything wrong with this?

@phenomen
Copy link

phenomen commented Mar 4, 2024

I get Uncaught ReferenceError: appManager is not defined error. Followed all steps (and watched video to confirm). Authy 2.2.3

upd: I restarted Authy and then it worked.

@OleksaBaida
Copy link

Thank you @gboudreau you are the saviour!!!
It was not really clear enough firstly that you need to stick to the 2.2.3 version to make the args work, however when I'd realized what is the roadbump - worked like a charm!

I was even able to copy Humble Bundle 2FA codes to Bitwarden!

PS if you are using modern windows version you could execute the following to install Authy Desktop 2.2.3 with the winget
winget install -e --id Twilio.Authy -v 2.2.3

@Cod053
Copy link

Cod053 commented Mar 4, 2024

This worked perfectly for me, although I couldn't quite figure out how to get the console to allow me to copy the QR codes themselves - it didn't really matter though as it gave me the secret and URI for every entry anyway and I was able to confirm that they match up in Bitwarden and Authy. Can just encrypt the details with something like VeraCrypt in case they are ever needed in the future.

Big thanks to the gist OP

@marcelopm
Copy link

marcelopm commented Mar 5, 2024

@gregsadetsky
Thank you so so much for documenting all of this!

I wanted to export my tokens from Authy and re-import them into KeePassXC. I haven't seen anyone describe how to do this, so I wrote the missing code, and documented how to do this export + import below. Hopefully it can be of use to someone else!
...

This is gold! thank you!!

@NeoMod
Copy link

NeoMod commented Mar 6, 2024

Thank you @gboudreau for the extensive documentation.

I didn't want to leave Authy but wanted to quickly export all my codes to BitWarden to have an easier desktop experience, without the need to reset each service. This allowed me to do exactly that, without flaws and in 5 minutes.

Thank you indeed.

EDIT:
P.S. You might want to update the EOL to 19/03/2024 since Authy has accelerated the phase-out for the desktop app.

@Marvvyn
Copy link

Marvvyn commented Mar 7, 2024

Thank you @gboudreau, very detailed, helpful and works great. I got all my secrets in a minute.

@ubershmekel
Copy link

On windows to install the old authy even though you already have an existing version, I had to add --no-upgrade --force so run the following:

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

@wiryonolau
Copy link

wiryonolau commented Mar 9, 2024

I cannot login using snap version, got not acceptable error. Did they block login from old version ?
Do I need to remove the current snap authy 2.5.0

@sneakyjoeru
Copy link

Logged in to snap version without removing 2.5.0, exported everything without a hitch.

@eagle1maledetto
Copy link

Thank you so much. Everything worked like a charm. Migrated to ente auth successfully

@jcanfield
Copy link

https://github.com/alexzorin/authy?tab=readme-ov-file

So this Go Library worked like a charm for me. You enter your password, approve the device then enter your backup password. It exports all keys as otpauth://.

@5310
Copy link

5310 commented Mar 12, 2024

Kept getting "Not acceptable" like some kind of villainous nanny from a children's movie (Flatpak, 2.2.4 and also 2.5.0)

Then just used the alexzorin/authy CLI program mentioned right above, and it worked perfectly! Thanks a lot for the link!

@ikjadoon
Copy link

All instructions worked flawlessly; thank you so much, OP.

My only note, @gboudreau: if users want to uninstall Authy afterwards, Windows complains that Update.exe is required to uninstall. For that, I simply restored both Update.exe's from Recycle Bin and then the Windows uninstall proceeded perfectly, uninstalling all Authy versions. Unsure how many this will affect or how important it is.

//

1Password users, you can directly copy & paste the TOTP secret into the 1Password desktop application's (via "Add a one-time password") without needing to scan the QR codes. Everything worked just like the OP explained; a reminder that any Authy-specific tokens (e.g., Twitch) aren't reproducible in 1Password, so make sure to switch that to a normal 2FA code first.

@Piskatje13
Copy link

Piskatje13 commented Mar 15, 2024

It worked! I followed instructions from this video
Any suggestions for a free Authy alternative where I can import it?

@jamiesandenr
Copy link