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

@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

Thank you, this worked like a dream.

@moazam1
Copy link

moazam1 commented Mar 15, 2024

After I ran the script I got following JSON:

{
"id": "a282-bd2b78c78e07",
"organizationId": null,
"folderId": "acff-01992fa4d4a0",
"type": 1,
"reprompt": 0,
"name": "Cloudflare",
"notes": null,
"favorite": false,
"login": {
"username": null,
"password": null,
"totp": "otpauth://totp/Cloudflare?secret=XXXXXXMGYYYYYOZNXXXXXX&digits=7&period=10"
},
"collectionIds": null
}

When I enter topt screen: XXXXXXMGYYYYYOZNXXXXXX into bitwarden and enter 2fa code on cloudflare website I got invalid token message from cloudflare. Did I miss something?

@NeoMod
Copy link

NeoMod commented Mar 16, 2024 via email

@NeoMod
Copy link

NeoMod commented Mar 16, 2024

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

Keep Authy on mobile devices (smartphone, tablet, etc.) and use a Password Manager with Integrated OTP/TOTP like Bitwarden on Desktop since it makes life a bit easier.

However, it is important to note that entrusting both your password and 2FA to a Password Manager is not advisable. The reason the desktop app has been discontinued is to encourage users to utilize two physically different devices for added security.

@eagle1maledetto
Copy link

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

Ente Auth

@NeoMod
Copy link

NeoMod commented Mar 19, 2024

No, it's not. It doesn't matter where the passwords and codes reside as long as you have both.

Yes, it is. Security-wise, the whole point of 2FA is to prevent a third party from gaining access to your services even
if your password is compromised.

Having both the password and the 2FA in the same place nullifies this security concept and exposes all your services to the same level of breach vulnerability as before; a third party now has to focus on just one password - the master password for your password manager(*) - to gain complete access to all your accounts.

The opposite is also true: if you lose access's to either your password or your 2FA, you can still provide "proof of ownership" for some account type and reset your credentials. But if you lose access to both, because they are in the same place, regaining access to some accounts might be very difficult if not impossible in same cases.

(*) broadly speaking, a third party has to focus just on obtaining access to your password manager: it doesen't matter if it's by gaining access to your master password or to your workstation, for example.

@Marvvyn
Copy link

Marvvyn commented Mar 20, 2024

Authy desktop 2.2.3 with update.exe removed still works ok.

@1natsu172
Copy link

For people looking to migrate to ente auth.

This has been modified from Export to Bitwarden JSON - Advanced to correctly migrate the item's name to "ente auth". It's left here for those considering migrating to ente.

// 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.originalName?.includes(":")
        ? i.originalName?.split(":")
        : ["", i.name ?? i.originalName];
      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,
});

@heapxor
Copy link

heapxor commented Mar 21, 2024

hi, isnt that more convenient way? https://github.com/alexzorin/authy

@gjabouley-invn
Copy link

gjabouley-invn commented Mar 21, 2024

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

2FAS seems the most hyped solution recommended today, although it does not come with a Desktop App.

@eagle1maledetto
Copy link

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

2FAS seems the most hyped solution recommended today

Ente Auth is the only one that have a similar set of features like Authy

@adv0r
Copy link

adv0r commented Mar 25, 2024

VM1317:5 Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at hex_to_b32 (<anonymous>:5:27)
    at <anonymous>:74:59
    at Array.map (<anonymous>)
    at <anonymous>:67:37

MacOS -> I get this when running the snippet that export in JSON format (both normal and unencrypted)

@larryqiann
Copy link

Just tested on macOS 14.3, exporting to Aegis. Instructions are working. I let the app update first and then logged in, then copied the downloaded version over the app again. I then saw the instructions to disable auto update so I did those steps, and then opened the app as per instructions.

Remember to unlock your vault with the backup password first!

Thank you!

@danielhass
Copy link

@gboudreau thank you so much for this updated fork of the manual. I was able to migrate to a different app after the Authy Desktop EOL. Very helpful 💯

Minor update: the recommended Snap variant of this tutorial doesn't work anymore as Authy has removed their app from snapcraft.io; so this source for the Snap is gone.

@matheusfrteixeira
Copy link

Hello guys, Has anyone managed to find a solution for the tokens generated in 10s but in Authy it is 20s?

@User1017366
Copy link

Thank you so much OP!!!

@jcanfield
Copy link

hi, isnt that more convenient way? https://github.com/alexzorin/authy

That's what I used (I posted the link above). It worked flawlessly. I have over 50 TOPTs, so it took awhile to add to Bitwarden. Glad I found that though.

@jcanfield
Copy link

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!

Glad that worked. I was happy to find that repo too. Made the migration much easier. Plus I have an encrypted backup of all of my keys now.

@millionsofplayers
Copy link

millionsofplayers commented Apr 1, 2024

flatpak doesn't install, this is the error i get when i try to install authy from the terminal;

flatpak install flathub com.authy.Authy                             ✔  02:02:27 PM  ▓▒░
Looking for matches…

Info: app com.authy.Authy branch stable is end-of-life, with reason:
   The Authy Desktop app have their End-of-Life. It is recommended to switch to use their mobile apps instead or other equivalent desktop apps.

com.authy.Authy permissions:
    ipc                  network       pulseaudio      x11     dri
    dbus access [1]      tags [2]

    [1] org.freedesktop.Notifications, org.kde.StatusNotifierWatcher
    [2] proprietary


        ID                      Branch         Op         Remote          Download
 1. [✗] com.authy.Authy         stable         i          flathub         1.2 MB / 75.2 MB

Error: While downloading https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_23.snap: Server returned status 404
error: Failed to install com.authy.Authy: While downloading https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_23.snap: Server returned status 404

@gboudreau
Copy link
Author

gboudreau commented Apr 1, 2024

flatpak doesn't install, this is the error i get when i try to install authy from the terminal;

@millionsofplayers Indeed, the snap was removed last week by Authy; I replaced its URL in the snap instructions with a copy of the original file I uploaded at file.io
Since flatpak seems to be using the snap behind the scene, maybe try to install using the snap directly?

@millionsofplayers
Copy link

millionsofplayers commented Apr 1, 2024

got an invalid hash error, should i keep going

i get this error when i try unsquashfs too:

FATAL ERROR: Can't find a valid SQUASHFS superblock on authy.snap

@gboudreau
Copy link
Author

gboudreau commented Apr 1, 2024

got an invalid hash error, should i keep going?

@millionsofplayers
I updated the instructions with a new URL (file.io has deleted my upload).
The new URL should work as expected.

@gzpaitch
Copy link

gzpaitch commented Apr 4, 2024

Thanks! I exported using the "Export to Bitwarden JSON - Advanced" option and then uploaded the JSON to Ente Auth with no problem! I highly recommend the Ente Auth as an alternative.

@michellejazmin
Copy link

Thank you, it worked like a charm!

@durzaq
Copy link

durzaq commented Apr 9, 2024

thank you, i totally switch to 2FAS

@alonwillmakeit
Copy link

Hey, after running the script (one that shows QRCODES and saves to JSON aswell )
I saw these messages in the console
image

anyone knows what is this ?

@gboudreau
Copy link
Author

app.js is Authy's code; it's trying to send requests to AWS Kinesis, which they probably use to track app analytics or something similar. You can safely ignore.

@FreddyAbrego
Copy link

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!

Glad that worked. I was happy to find that repo too. Made the migration much easier. Plus I have an encrypted backup of all of my keys now.

I've been trying this repo, and I have had luck getting my keys. However when I remove the device from my authy account. They keys stop working. Also it seems to not take into account the backup password. If I put it in I get a key, if I put in something else, I get the same key and nothing about the password being incorrect.

Anyone with this same issue?

@dubniczky
Copy link

Still worked, appreciate it!

@chinlung
Copy link

Great job! Thank you very much.

@ikt32
Copy link

ikt32 commented May 2, 2024

This works great, thank you! Finally off the sinking Authy ship.

@mytechtots
Copy link

It works! Same here, migrating from Authy.. and had to get around a terrible app design that makes 2fa change not possible without contacting support. Grateful for this awesome work!

@LokeYourC3PH
Copy link

I decided to use the other method via the Go script people recommended here, since somehow, my Browser could not connect to Authy's Debug port at all and did not want to show any page at all, opened in a Chromium Browser (Brave) or otherwise. Also, this method is a lot easier: https://github.com/alexzorin/authy

All you have to do, even on Windows, is just install Go, copy the command, wait for it to install the authy-export tool, then run the command "authy-export". Done, no fuss.

@jcanfield
Copy link

I decided to use the other method via the Go script people recommended here, since somehow, my Browser could not connect to Authy's Debug port at all and did not want to show any page at all, opened in a Chromium Browser (Brave) or otherwise. Also, this method is a lot easier: https://github.com/alexzorin/authy

All you have to do, even on Windows, is just install Go, copy the command, wait for it to install the authy-export tool, then run the command "authy-export". Done, no fuss.

Same here. I found that Go script to work seemlessly. I was able to export to Bitwarden and Google Auth.

Personally, I've enjoyed Authy and have converted a good dozen ppl to use it but without the Desktop version, I'm being forced to migrate. I hope the Authy (their contributors) know that they are losing a good portion of their community.

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