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

@gadelkareem
Copy link

gadelkareem commented Feb 17, 2022

Just combining the code from the comments to migrate from Authy to Bitwarden

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) => {
            var 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,
});

@shinji257
Copy link

As others have mentioned the Authy specific tokens (the ones that generate 7 digits and last for 10 seconds each) do not seem to work as the tokens generated in BitWarden are different than in Authy. I'm using the otpauth url generated by the scripts above. Tried the most recent one and the one in the original message. I've worked around it a bit on a couple of them by getting the site to generate a Google Auth standard token instead (or in addition to depending) but there is one that is still Authy only.

@rofra
Copy link

rofra commented Mar 21, 2022

I had issues running this and it seems like Authy had tweaked the object model. I had to change const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.secretSeed)); to this:

 const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));

(secretSeed had changed to encryptedSeed)

Exact, as of today, the correct code to export to bitwarden json format is:

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) => {
            const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));
            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,
});

@jpph
Copy link

jpph commented Mar 21, 2022

I have issue with ? unexpected token.
Seems it doesn't understand that line : ?? i.secretSeed

@gboudreau
Copy link
Author

gboudreau commented Mar 21, 2022

I have issue with ? unexpected token.
Seems it doesn't understand that line : ?? i.secretSeed

Fixed.

@jpph
Copy link

jpph commented Mar 21, 2022

I have now this error :
+1: Uncaught TypeError: Cannot read property 'length' of undefined
at hex_to_b32 (:5:119)
at :14:65
at Array.map ()
at :13:34

btw, does the message when opening the console is normal ? :
index.esm.js:106 [2022-03-21T13:33:42.826Z] @firebase/app:
Warning: This is a browser-targeted Firebase bundle but it appears it is being
run in a Node environment. If running in a Node environment, make sure you
are using the bundle specified by the "main" field in package.json.

  If you are using Webpack, you can specify "main" as the first item in
  "resolve.mainFields":
  https://webpack.js.org/configuration/resolve/#resolvemainfields
  
  If using Rollup, use the @rollup/plugin-node-resolve plugin and specify "main"
  as the first item in "mainFields", e.g. ['main', 'module'].
  https://github.com/rollup/@rollup/plugin-node-resolve

defaultLogHandler @ index.esm.js:106
vue.common.js:14741 Download the Vue Devtools for a better development experience:
https://github.com/vuejs/vue-devtools
Show item in folder is not enabled in hosted mode. Please inspect using chrome://inspect

@rylos
Copy link

rylos commented Mar 21, 2022

I'm getting too Uncaught TypeError: Cannot read property 'length' of undefined on the line for (let i = 0; i < hex.length; i += 2) {

@jpph
Copy link

jpph commented Mar 21, 2022

I changed :
const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));
to
const secret = i.decryptedSeed ;

And was happy with it, not sure if hextob32 is needed at all....

@gboudreau
Copy link
Author

I edited the original code to use i.secretSeed, same as before, but if that is undefined, then try i.encryptedSeed.
That should work for everyone.

@jpph
Copy link

jpph commented Mar 21, 2022

yes! Working perfectly, Thank you!

If I may, i do not see in the comment, what is markedfordeletion ? you shouldn't export it ?

@gboudreau
Copy link
Author

I don't really remember, but I guess this is it.

@yatharth
Copy link

Sounds obvious, but remember to “Unlock” the Authy Chrome App first.

@chigzy
Copy link

chigzy commented Mar 30, 2022

Perfect, got OTP Auth on iOS working through this.

@xoyowade
Copy link

xoyowade commented Mar 31, 2022

I had issues running this and it seems like Authy had tweaked the object model. I had to change const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.secretSeed)); to this:

 const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));

(secretSeed had changed to encryptedSeed)

Exact, as of today, the correct code to export to bitwarden json format is:

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) => {
            const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));
            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,
});

@rofra Your code works for me. Thanks!!

@0nebukadnezar0
Copy link

Hi, many thanks, it was very helpful to export TOTP into Yubikey, but how can the backup file "authy_backup.json" be open ?

@lwndow
Copy link

lwndow commented Apr 19, 2022

After a rename of tokens without an issuer (and icon only) and relogging in of Authy, I too am liberated using the original instructions. Cheers for this!

@strider72
Copy link

Worked for me on a Mac. Tested in 1Password (Mac) and Aegis (Android); works perfectly in both. Interesting how this method results in a new code every 10 seconds, and the Authy app actually only shows every third code.

@doctor-beat
Copy link

Sheer brilliance, this is

@TedTschopp
Copy link

looks like it works great with Apple Keychain as well. Just did a huge import. If it didn't work when I need to enter a 2fa key I'll come back and update, but it looks great so far.

@presto8
Copy link

presto8 commented Jun 4, 2022

Very nice solution, thank you for posting it. It worked perfectly.

@coltondick
Copy link

coltondick commented Jun 4, 2022

I had issues running this and it seems like Authy had tweaked the object model. I had to change const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.secretSeed)); to this:

 const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));

(secretSeed had changed to encryptedSeed)

Exact, as of today, the correct code to export to bitwarden json format is:

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) => {
            const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(i.encryptedSeed));
            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,
});

I am unable to get this to work. Throwing the following error:

Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at hex_to_b32 (<anonymous>:5:29)
    at <anonymous>:68:79
    at Array.map (<anonymous>)
    at deEncrypt (<anonymous>:67:40)
    at <anonymous>:100:1

@coltondick
Copy link

coltondick commented Jun 5, 2022

I am unable to get this to work. Throwing the following error:

Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at hex_to_b32 (<anonymous>:5:29)
    at <anonymous>:68:79
    at Array.map (<anonymous>)
    at deEncrypt (<anonymous>:67:40)
    at <anonymous>:100:1

I did manage to get this working.

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,
});

@nikhilbundile
Copy link

That's great, thanks for this! Has anyone found a way to mass import in Aegis?

@amitsax
Copy link

amitsax commented Jun 7, 2022 via email

@gboudreau
Copy link
Author

That's great, thanks for this! Has anyone found a way to mass import in Aegis?

https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93?permalink_comment_id=3596406#gistcomment-3596406

@coltondick
Copy link

coltondick commented Jun 7, 2022

I've updated the Aegis script to function the same as the Bitwarden script does.

This will output the following file authy-to-aegis-export.json

Ensure you select Aegis when importing the file.

// Based on https://github.com/LinusU/base32-encode/blob/master/index.js
const 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
const uuidv4 = () => {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (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
const 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 deEncrypt = ({ log = false, save = false }) => {
  let aegisDb = {
    version: 1,
    header: {
      slots: null,
      params: null,
    },
    db: {
      version: 1,
      entries: 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, name] = i.name.includes(":")
          ? i.name.split(":")
          : ["", i.name];

        return {
          type: "totp",
          uuid: uuidv4(),
          name: name,
          issuer: issuer,
          icon: null,
          info: {
            secret: secret,
            algo: "SHA1",
            digits: i.digits,
            period: period,
          },
        };
      }),
    },
  };
  if (log) console.log(JSON.stringify(aegisDb));
  if (save) saveToFile(aegisDb, "authy-to-aegis-export.json");
};

deEncrypt({
  log: true,
  save: true,
});

@Dasnap
Copy link

Dasnap commented Jun 13, 2022

I've updated the Aegis script to function the same as the Bitwarden script does.

This will output the following file authy-to-aegis-export.json

Ensure you select Aegis when importing the file.

// Based on https://github.com/LinusU/base32-encode/blob/master/index.js
const 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
const uuidv4 = () => {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (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
const 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 deEncrypt = ({ log = false, save = false }) => {
  let aegisDb = {
    version: 1,
    header: {
      slots: null,
      params: null,
    },
    db: {
      version: 1,
      entries: 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, name] = i.name.includes(":")
          ? i.name.split(":")
          : ["", i.name];

        return {
          type: "totp",
          uuid: uuidv4(),
          name: name,
          issuer: issuer,
          icon: null,
          info: {
            secret: secret,
            algo: "SHA1",
            digits: i.digits,
            period: period,
          },
        };
      }),
    },
  };
  if (log) console.log(JSON.stringify(aegisDb));
  if (save) saveToFile(aegisDb, "authy-to-aegis-export.json");
};

deEncrypt({
  log: true,
  save: true,
});

This works fabulously, thanks.

@toncid
Copy link

toncid commented Jun 29, 2022

Hello! I am stuck at step 7:

Then right-click main.html and select Open in containing folder. That will open a Console where you can paste the following code:

However, there is no main.html in sight:

image

Any ideas what could be wrong?

Chrome v103, Authy Desktop v2.2.0, macOS v12.4 (M1)

@toncid
Copy link

toncid commented Jun 29, 2022

Ha! 😄 Didn't even notice opening dev tools of dev tools. Thanks!

@toncid
Copy link

toncid commented Jun 29, 2022

Thanks, it works now!

@gboudreau any reason why QR codes are doubled in the console output?

@weisheng-homage
Copy link

Great work on this.

A few extra information to share:

  • Google authenticator works in iOS to generate 7 digit code.
  • After you got your qrcode/ totp url, do not unlink the desktop app from your authy app, it will invalidate the secret right away.
  • Authy seems to have a 20s timeout for the otp, changing the totp url to period=20 do not work, we have to keep to the 10s period.
  • going to the url on localhost did not work for me, going to chrome://inspect/#devices and click on the "Twilio Authy" with file ending "main.html" to inspect works for me

@jpbochi
Copy link

jpbochi commented Jul 5, 2022

script was failing for me. I ended up running something much simpler:

appManager.getModel().find(a => a.name.startsWith('Goo')).decryptedSeed

The output of that was the value I could paste on 1Password, and the generated OTP was identical. I repeated it for a few different accounts. All worked fine.

@juliennnnn
Copy link

Thanks a lot, it worked for me on Authy Desktop for mac + 1password!

@GeekCornerGH
Copy link

Still working, thanks, I managed to export my Authy accounts in my Bitwarden vault without issues!

@Brandin
Copy link

Brandin commented Jul 31, 2022

I have now this error : +1: Uncaught TypeError: Cannot read property 'length' of undefined at hex_to_b32 (:5:119) at :14:65 at Array.map () at :13:34

btw, does the message when opening the console is normal ? : index.esm.js:106 [2022-03-21T13:33:42.826Z] @firebase/app: Warning: This is a browser-targeted Firebase bundle but it appears it is being run in a Node environment. If running in a Node environment, make sure you are using the bundle specified by the "main" field in package.json.

  If you are using Webpack, you can specify "main" as the first item in
  "resolve.mainFields":
  https://webpack.js.org/configuration/resolve/#resolvemainfields
  
  If using Rollup, use the @rollup/plugin-node-resolve plugin and specify "main"
  as the first item in "mainFields", e.g. ['main', 'module'].
  https://github.com/rollup/@rollup/plugin-node-resolve

defaultLogHandler @ index.esm.js:106 vue.common.js:14741 Download the Vue Devtools for a better development experience: github.com/vuejs/vue-devtools Show item in folder is not enabled in hosted mode. Please inspect using chrome://inspect

How did you get passed the error of Show item in folder is not enabled in hosted mode?

@ben05allen
Copy link

thanks, working as per Jun 8th code from @coltondick

@acosta-edgar
Copy link

Tried today on brave browser. pasted the code in the console tab directly and it works like a charm.
It is great not having to have different auth/totp apps.

@S3542U
Copy link

S3542U commented Aug 4, 2022

I tested this out and it works great! Thanks!

It's a nice way of transferring codes, but I don't really like that Authy limits the codes to 10 seconds.

So I'll just transfer manually all the codes one by one; Aegis gives more time.

@statico
Copy link

statico commented Aug 15, 2022

On macOS, note that you have to use the "Direct Download" link on the download page. Do not click the App Store button — if you install Authy via the App store, you'll get a completely different app called Authy.app that doesn't let you inspect it. Clicking the Direct Download link should get you a .dmg with Authy Desktop.app which is a completely separate app (and the one you want for these instructions).

@navjotjsingh
Copy link

I haven't tried the method but I can confirm that 1Password for Windows supports 7-digit OTPs and 10s timeouts. It worked with the demo QR code perfectly.

@PTC-RNelson
Copy link

PTC-RNelson commented Aug 25, 2022

Has anyone used this successfully with Twilio/Sendgrid (creators of Authy) + 1password? I was able to generate the 1p TOTP, but Sendgrid says they are invalid. The 1p codes are 6 digit while Auth is 7 digit

Edit - Nevermind, use the URI for 1p and it will generate the correct 7 digit code.

@xenio
Copy link

xenio commented Sep 7, 2022

After importing all the TOTP tokens to AEGIS, is it safe to remove all the account association to the Authy app?

@xaminmo
Copy link

xaminmo commented Sep 29, 2022

@shadyrexman wrote:

I've updated the Aegis script to function the same as the Bitwarden script does.

This will output the following file authy-to-aegis-export.json

Ensure you select Aegis when importing the file.

this doesn't work for me, it said : Uncaught SyntaxError: Identifier 'hex_to_b32' has already been declared

You need to refresh the page after saving in the other json files. When you paste new code into the existing console, it does not remove what you pasted before. Both BitWarden and Aegis have declarations for hex_to_b32.

@KingPin
Copy link

KingPin commented Oct 8, 2022

when trying to export for bitwarden I get this error: Uncaught SyntaxError: Unexpected token ')'

did anyone get this before?

@oneofthedamons
Copy link

When I try Step 7 the Console shows an error:

Show item in folder is not enabled in hosted mode. Please inspect using chrome://inspect

Seeing this using both Edge and Brave on MacOS 12. Any tips?

@03c
Copy link

03c commented Oct 14, 2022

Just spent ages wondering why all my secrets were null...

You need to enter your password in Authy - I thought it was decrypted already as it was showing the list of sites. 🤦

@03c
Copy link

03c commented Oct 14, 2022

when trying to export for bitwarden I get this error: Uncaught SyntaxError: Unexpected token ')'

did anyone get this before?

Yes, there were a couple of syntax errors for me as well, specifically an extra ) and a missing ;

I have created a new gist for my reference: https://gist.github.com/03c/afd4a1d13b8dcdf1c2aeb40dc92c33dc

@druchoo
Copy link

druchoo commented Oct 16, 2022

@Brandin, @oneofthedamons and anyone else,

For error on steps 6 & 7:

Show item in folder is not enabled in hosted mode. Please inspect using chrome://inspect

Replace with:

  • Go to Sources -> Snippets. Click + New snippet. Name can be the default "Script snippet #x" or anything. An editor will open in a column
  • Paste code into editor window
  • Run script by one of the following
    • CMD-Enter (osx)
    • right click on script name -> Run
    • Click the "Play" button

@gboudreau
Copy link
Author

Yes, there were a couple of syntax errors for me as well, specifically an extra ) and a missing ;

Thanks for that ; I merged your fixes into my Gist.

@gboudreau
Copy link
Author

For error on steps 6 & 7:

Show item in folder is not enabled in hosted mode. Please inspect using chrome://inspect

Replace with:

  • Go to Sources -> Snippets. Click + New snippet. Name can be the default "Script snippet #x" or anything. An editor will open in a column
  • Paste code into editor window
  • Run script by
    • right click on script name -> Run

Thanks for those details. I integrated those instructions to replace previous steps that were not working anymore.

@mkyrychenko
Copy link

mkyrychenko commented Oct 20, 2022

Just another working script, which allows console output in either "JSON" or human-readable format.
Also allows saving the JSON into the file with a configurable file name.

https://gist.github.com/mkyrychenko/6d3d2bcfdafeaeaae09ae81eb285af90

Enjoy and best regards

@macintoshplus
Copy link

Thanks you, I have been backup my authy account.

@talk2bryan
Copy link

Still working - thanks for this tool!
If you want to import your seeds to Standard Notes and use it as your 2FA application, I wrote a gist that can help you do that too! Standard Notes offers you privacy and security!

@leachim6
Copy link

Not working for me in Chrome latest:

Google Chrome | 107.0.5304.107 (Official Build) (64-bit) (cohort: Stable)
Revision | 294f9da25eeda7ddca937d7c70b92d53a71745dc-refs/branch-heads/5304@{#1197}
OS | Windows 10 Version 21H2 (Build 19044.2130)
JavaScript | V8 10.7.193.22
User Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

Authy Desktop version 2.2.1

Script snippet #1:55 Uncaught ReferenceError: appManager is not defined
    at deEncrypt (Script snippet #1:55:14)
    at Script snippet #1:88:1
deEncrypt @ Script snippet #1:55
(anonymous) @ Script snippet #1:88
appManager.getModel().find(a => a.name.s

I searched through the entire source and can't find appManager defined anywhere. Did they change the app since 4 days ago or am I doing something wrong?

@SlamminCO
Copy link

Not working for me in Chrome latest:

Google Chrome | 107.0.5304.107 (Official Build) (64-bit) (cohort: Stable)
Revision | 294f9da25eeda7ddca937d7c70b92d53a71745dc-refs/branch-heads/5304@{#1197}
OS | Windows 10 Version 21H2 (Build 19044.2130)
JavaScript | V8 10.7.193.22
User Agent | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36

Authy Desktop version 2.2.1

Script snippet #1:55 Uncaught ReferenceError: appManager is not defined
    at deEncrypt (Script snippet #1:55:14)
    at Script snippet #1:88:1
deEncrypt @ Script snippet #1:55
(anonymous) @ Script snippet #1:88
appManager.getModel().find(a => a.name.s

I searched through the entire source and can't find appManager defined anywhere. Did they change the app since 4 days ago or am I doing something wrong?

I am also having this issue, however I'm using Brave

@wongcoder
Copy link

Also having this issue--thinking authy has likely patched this.

@aceace15
Copy link

same is happening to me so thinking the same as the person above said

@0xseck
Copy link

0xseck commented Dec 18, 2022

If you are getting " appManager is not defined" error, do NOT use the devtools of the browser, the page should have its own devtools-like panel. Use that instead

@wongcoder
Copy link

Ah, thanks.

@leachim6
Copy link

If you are getting " appManager is not defined" error, do NOT use the devtools of the browser, the page should have its own devtools-like panel. Use that instead

This solved my problem, thank you!

@drumpat01
Copy link

This worked perfectly! I was able to export everything from Authy on my Linux desktop (fedora 38) and then scan all of the QR Codes into Aegis! You're amazing!

@SKLCLU
Copy link

SKLCLU commented Dec 30, 2022

Can confirm still works. Exported into Aegis via the QR codes. Authy is so bad with naming being inconsistent on phones vs the desktop app, which makes it confusing at times to know which code is for what. Luckily I only had a couple of things left to move that I haven't done manually beforehand.

@alexwilczewski
Copy link

Thank you. Worked perfect to get Sendgrid OTP into 1Password.

@42-sol-137
Copy link

This works perfectly! Thank you very much. I have one site that I use with 2FA which requires a long wait on hold for a phone call to reset or change passwords or 2FA. Now I can freely move this site from Authy to other authenticators and most importantly have a backup of my tokens.

@whitesiroi
Copy link

nice, thank you very much

@stoilsky
Copy link

Awesome, works like a charm! TY

@bagarwa
Copy link

bagarwa commented Jan 20, 2023

I need to be internet connected to do this? Authy itself works fine even if I have no internet connection. So, shouldn't it be possible to export the tokens in offline mode?

As precaution, so that the tokens are not exported to some third party, I turned off my wifi before running the code mentioned above. (Nothing against you, and I'm not saying you are doing something malicious. But I hope you appreciate me being cautious against something I don't understand.)

When I run the snippet, it throws this error -

POST https://kinesis.us-east-1.amazonaws.com/ net::ERR_INTERNET_DISCONNECTED
[warning]  => Error sending data to kinesis

Is there any reason it is trying run POST operation to some AWS server?

@japzone1
Copy link

@bagarwa Um, if you check the error console before you even enter gboudreau's code, you'd realize that error pops-up whenever Authy is offline. Kinesis is an Amazon service for collecting telemetry, so Twilio(owners of Authy) probably uses it for app diagnostics, and Authy is complaining that it can't talk to Amazon's servers.

Looking at gboudreau's code, at least in my eye, I can't see a single function that calls to a remote server. The only URLs are in the comments giving credit to where gboudreau found that snippet of code he's using.

If you want further proof, just run the code while offline and you'll see that it still works. I just tested it and it worked fine offline.

@bagarwa
Copy link

bagarwa commented Jan 20, 2023

Ahh... great! Thank you so much for your reply @japzone1. For whatever reason, previously I was getting only the erros, not the token QRs. After you reply, I tried it again and this time I did get the QRs.

I think the Authy was logging out in the background after every 10-15 seconds or so, that's why I must've seen only errors the first time. Thanks again.

And thanks a whole bunch @gboudreau. Finally something that will help me move away from Authy.

@cgranier
Copy link

Thank you very much for providing this solution. It's ridiculous that Authy will not provide this functionality for users (yet it's 100% possible to do it).

@WJTech
Copy link

WJTech commented Jan 21, 2023

Remote debugging seems to be missing on Authy 2.2.2. I rolled back to a prior version, and all good. Thanks for the git.

@filidorwiese
Copy link

❤️ Worked perfectly thanks!

(It's awful that Authy doesn't offer a less hacky solution to export your keys)

@sanghel-orbyta
Copy link

Remote debugging seems to be missing on Authy 2.2.2. I rolled back to a prior version, and all good. Thanks for the git.

how did you revert to a previous version?

I'm trying to winget 2.2.1, but download fails...

image

@sanghel-orbyta
Copy link

..never mind, tried the above procedure with Authy Desktop 2.2.2 anyway, and all is well..

thank you @gboudreau

@the86freak
Copy link

with 2.2.3 there's an issue saying:
"Uncaught SyntaxError: Identifier 'hex_to_b32' has already been declared"

So I simply renamed the according function and its call and it works fine! Thanks a lot!

@vdias
Copy link

vdias commented Feb 19, 2023

The export to file solution do not work...

@sascha224
Copy link

The export to file solution do not work...

Yesterday it did for me... (Authy 2.2.3 @Windows 11) What's the error message you got?

@Suncatcher
Copy link

works like a charm, thanks!

@henriquekano
Copy link

After typing my phone number on authy and trying to run the snippet, all but two codes (?) were exactly the same - checked the json created, the 'secret's for those ones that were showing the same code were null. Long story short: forgot to type the backup password 🤦‍♂️

TLDR: it worked

@cainv
Copy link

cainv commented Mar 10, 2023

Amazing. How do I buy you a coffee?

@WJTech
Copy link

WJTech commented Mar 17, 2023

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

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

@scanmikey
Copy link

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

@KamilsView
Copy link

KamilsView commented Mar 27, 2023

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

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

Thanks
Kamil

@AlephOmicron
Copy link

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

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

@hazrpg
Copy link

hazrpg commented Apr 13, 2023

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

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

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

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

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

@KamilsView
Copy link

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

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

@simeon-walker
Copy link

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

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

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

@amitsax
Copy link

amitsax commented Apr 15, 2023

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

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

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

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

@RokeJulianLockhart
Copy link

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

@AlephOmicron
Copy link

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

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

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

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

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

@japzone1
Copy link

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

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

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

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

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

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

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

@daegalus
Copy link

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

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

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

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

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

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

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

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

@olanystrom
Copy link

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

@Grishkaone
Copy link

It starts, but does not listen on port 5858

Same here, but on Mac. Nothing on 5858.

@jeffre
Copy link

jeffre commented Apr 19, 2023

Downgraded to Authy 2.2.3 to get the port listened

@Grishkaone
Copy link

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

@ptsgmartins
Copy link

ptsgmartins commented Apr 20, 2023

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

@Grishkaone How can one prevent autoupdate?

@Grishkaone
Copy link

Grishkaone commented Apr 20, 2023 via email

@ptsgmartins
Copy link

ptsgmartins commented Apr 20, 2023

@Grishkaone Thanks; it works! 👍

@eugeneketeni
Copy link

eugeneketeni commented Apr 21, 2023

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

@ManuzCam
Copy link

Thank you, it works

@ajshastri
Copy link

ajshastri commented Apr 26, 2023

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

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

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

Is there a way to get around that?

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

Thanks!

image

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

@automationator
Copy link

automationator commented Apr 26, 2023

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

I changed that line to this:

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

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

@gboudreau
Copy link
Author

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

   Uncaught ReferenceError: appManager is not defined

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

@ajshastri
Copy link

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

   Uncaught ReferenceError: appManager is not defined

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

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

@maneldelgado
Copy link

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

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

@neekt
Copy link

neekt commented Apr 27, 2023

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

snap list authy --all

Which should output something like

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

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

sudo snap revert authy --revision 18

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

Kudos to @gboudreau for this gist.

@cstrouse
Copy link

cstrouse commented May 2, 2023

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

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

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

@sethaniel
Copy link

For Windows users trying to stick with version 2.2.3, I was able to just let Authy update, but then change my debug shortcut to point to the 2.2.3 version which was still installed. On my installation, this meant changing the shortcut target for my debug to: "%USERPROFILE%\AppData\Local\authy\app-2.2.3\Authy Desktop.exe" --remote-debugging-port=5858 and the Start in to: %USERPROFILE%\AppData\Local\authy\app-2.2.3.

The normal shortcut still brings up the newer version, but my debug shortcut brings up the 2.2.3 version. And it hasn't prompted to update again when I run it. I think it sees that the new version is already installed. I haven't done anything with the Update.exe file so it's still there.

@EricoCartmanez
Copy link

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

   Uncaught ReferenceError: appManager is not defined

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

Hello,

where are the details about the Developer Console then?

All I can see in Chrome is the three dots/More Tools/Developer Tools.

Screenshot 2023-05-07 at 8 45 59 PM

And it does not work there.

Thanx in advance

@EricoCartmanez
Copy link

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

snap list authy --all

Which should output something like

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

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

sudo snap revert authy --revision 18

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

Kudos to @gboudreau for this gist.

You 're the best ;)

@EricoCartmanez
Copy link

Ok found it. For those who had the Uncaught ReferenceError: appManager is not defined. Once the page opens, sources and snippets are there, you don't have to go to Developer Tools etc.

Screenshot from 2023-05-07 21-08-38

It worked, thank you.

@paramsiddharth
Copy link

paramsiddharth commented May 19, 2023

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

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

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

I had to do this for some reason:

npx electron . --remote-debugging-port=5858 '--remote-allow-origins=*'

Worked! Thank you!

@samw5
Copy link

samw5 commented May 19, 2023

I've used this previously but it seems that the remote debugging is no longer working. Anyone still able to reach it on Windows?

@paramsiddharth
Copy link

@samw5 Look at my comment directly above yours. Some similar commands with different directory paths will get it to work on Windows.

@efazati
Copy link

efazati commented May 22, 2023

Authy removed version 2.2.3 from snap 🤦 https://snapcraft.io/authy

@RokeJulianLockhart
Copy link

Authy removed version 2.2.3 from snap 🤦 snapcraft.io/authy

Does anyone know of an archived version, or how to extract a snap installer from a machine with the snap already installed? (I can do so to Android APKs, at least)

@efazati, https://forum.snapcraft.io/t/how-to-extract-installer-from-locally-installed-snap/35252?u=beedellrokejulianloc

@cclockworks
Copy link

I was able to downgrade the flatpak version of Authy using this method --> https://itsfoss.com/downgrade-flatpak-packages/

@Pratyay360
Copy link

Pratyay360 commented May 29, 2023

@ruario
Copy link

ruario commented Jun 15, 2023

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.

@laviRZ
Copy link

laviRZ commented Jun 16, 2023

Ok found it. For those who had the Uncaught ReferenceError: appManager is not defined. Once the page opens, sources and snippets are there, you don't have to go to Developer Tools etc.

Screenshot from 2023-05-07 21-08-38

It worked, thank you.

Very important!!! I was very frustrated for like 10 minutes not being able to figure it out

@lwndow
Copy link

lwndow commented Jun 16, 2023

This also works in the 1Password Linux client as well as the ones you have listed already. Thanks for publishing this! 👍

@andrew0g
Copy link

Works with 2FAS also 💪
Screenshot_20230624-213514

@alexmitelman
Copy link

alexmitelman commented Jun 27, 2023

June 2023: still works for Mac and 1Password

@gabrielrdrguez
Copy link

June 2023: still works on windows

@abdessalaam
Copy link

Amazing! Thanks. Worked for me (July 2023, Mac).
Two important points for newbs like me:

  1. Make sure you download Authy 2.2.3, no later
  2. Use the console that opens automatically when you click "Twilio Authy" link (it won't work in the usual chrome console)

@Bemesko
Copy link

Bemesko commented Aug 11, 2023

Used this in August 2023 for Windows. Still works following the advice above.

To download authy 2.2.3 on windows you can use chocolatey: https://community.chocolatey.org/packages/authy-desktop#versionhistory

Also, can confirm that the QR codes I got from this work on Keeper as well as the other apps.

@vmartins
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.

It worked, thank you.

@dumbasPL
Copy link

on arch linux you can just install electron25 and run

cd /opt/authy && electron25 --remote-debugging-port=5858 resources/app.asar

@fozziepowell
Copy link

fozziepowell commented Sep 4, 2023

All the QR codes it shows for my accounts are the same. I scanned 5 of them and noticed they all show the same codes.

The QR Codes all look different when scanning, but TOTP secret: null

@nzjrs
Copy link

nzjrs commented Sep 4, 2023

September 2023 - following this gist, and this comment, works and results in a QR code and manual details which can both be successfully imported into Gnome-Authenticator

The QR code does not work for Google Authenticator. It is imported, but the codes are not valid.

@cstrouse
Copy link

cstrouse commented Sep 4, 2023

You can always use a Javascript-based client-side TOTP code generator to calculate the codes based on the seed value extracted from Authy such as Dan Hersam's project. The linked project is simply an example of such an option, I have nothing to do with the developer and/or project and you should do your own research/validation that it's client-side only code before trusting.

@demolitionfabi
Copy link

@fozziepowell After you login into Authy Desktop for the first time, you need to open any of your items once which will ask for your decryption key. After entering the decryption key, your secrets should not be null anymore after exporting them.

@RoxasShadow
Copy link

RoxasShadow commented Sep 29, 2023

Amazing! Thanks. Worked for me (July 2023, Mac). Two important points for newbs like me:

1. Make sure you download Authy 2.2.3, no later

2. Use the console that opens automatically when you click "Twilio Authy" link (it won't work in the usual chrome console)

Worked successfully on Sonoma as well. Got both the QRs and the JSON.

@bricker
Copy link

bricker commented Oct 1, 2023

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.

@jamesjingyi
Copy link

Thanks very much for this! Have successfully started the ditching of Authy!

With regards to security, I'm wary about having any service hold my username, password and authentication. I am tempted to set up another Bitwarden account just for this, but wondered if anyone had any 'correct' solutions? Maybe I should just use Google Authenticator?

@afeedhshaji
Copy link

This still works for Arch as well. Had to revert the Authy AUR git commit to 2.2.3 version to enable debugger though!

@jpph
Copy link

jpph commented Oct 24, 2023

Thanks very much for this! Have successfully started the ditching of Authy!

With regards to security, I'm wary about having any service hold my username, password and authentication. I am tempted to set up another Bitwarden account just for this, but wondered if anyone had any 'correct' solutions? Maybe I should just use Google Authenticator?

Yes, better to have 2 different service. Myself I use bitwarden for username and password and aegis for totp

@zaourzag
Copy link

For Windows users trying to stick with version 2.2.3, I was able to just let Authy update, but then change my debug shortcut to point to the 2.2.3 version which was still installed. On my installation, this meant changing the shortcut target for my debug to: "%USERPROFILE%\AppData\Local\authy\app-2.2.3\Authy Desktop.exe" --remote-debugging-port=5858 and the Start in to: %USERPROFILE%\AppData\Local\authy\app-2.2.3.

The normal shortcut still brings up the newer version, but my debug shortcut brings up the 2.2.3 version. And it hasn't prompted to update again when I run it. I think it sees that the new version is already installed. I haven't done anything with the Update.exe file so it's still there.

you can just %localappdata%\authy

@hank9999
Copy link

hank9999 commented Nov 7, 2023

Thank you, I have successfully exported my tokens from authy, and added them to bitwarden!

@nficano
Copy link

nficano commented Nov 7, 2023

This will not work with Brave (hopefully this saves somebody a few minutes)

@minpeter
Copy link

minpeter commented Nov 8, 2023

Thanks I successfully exported and moved to gnome authenticator.

@SamKr
Copy link

SamKr commented Nov 24, 2023

This worked like a charm. You saved me a ton of time, thanks for sharing! 🍻

@AdamJel
Copy link

AdamJel commented Nov 28, 2023

Two comments:

  1. I tried on Windows and I had to start the app from the CMD, because adding the parameter into shortcut's field didn't work for some reason.

  2. I exported into json for BitWarden, but successfully imported into Proton Pass instead. Works like a charm.
    Btw. I am a bit uneasy with having everything within one service (passwords + totp), but Proton Pass is open source, audited and I trust the compony with email and VPN for a long time anyway. At the moment they lack a desktop native client (in development), but it already looks as the best engineered solution out of every other totp I've seen.

@DerekfromMadison
Copy link

DerekfromMadison commented Nov 29, 2023

Great instructions!

I had some problem where my desktop authy was not displaying the same information as my phone, which meant the exported key was not the same. I solved this by disabling access on my phone and then recreating the 2FA key with the target website. I suspect this is a bug in Authy when a key already exists on the phone and the desktop program is not updated correctly.

SOLVED - Authy seems to ignore the key rotation period and defaults to 30s while this output has it set to 10s. If you are having trouble, change the period to 30s or 20s and try those codes (you can change the otpauth text and generate a new QR code if you want to load the different period)

@onyxmueller
Copy link

Nice work @gboudreau! This still worked for me (December 2023, Mac).

Related to what @bpawel-bclub mentioned, make sure Authy doesn't secretly update to a later version. Check the version of the app if you are unable to get remote debugging port (via --remote-debugging-port) to work.

@ckiee
Copy link

ckiee commented Dec 15, 2023

Still works on Linux, with the addition of --remote-allow-origins=http://localhost:5858 and signing in from a newer version of Authy first (with the nixpkgs snap repackage, tested the 2.4.1+2.2.2 combo)

@SmilingGit
Copy link

Worked like a charm on MacOS, thanks a lot!!! Used it to export the keys and import them into Ente ( https://github.com/ente-io/auth ). I copy-pasted the text from the first script into a text editor (BBedit), then removed the text before otpath (search and replace .*otpauth with otpauth), then sorted the file. Then I only selected the lines beginning with otpauth, put them into a text file and imported them into Ente.

Side-note — when you write this:

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!

Is it only to make sure people don’t remove their Authy account without thinking? Because otherwise, I don’t see any reason why we couldn’t delete the Authy account afterwards.

Thanks a lot for this very very helpful piece of code!

@ckiee
Copy link

ckiee commented Dec 17, 2023

Is it only to make sure people don’t remove their Authy account without thinking? Because otherwise, I don’t see any reason why we couldn’t delete the Authy account afterwards.

Some of the entries don't export properly and have a key of null, so it is best to actually go through all the accounts and make sure you can login.

@SmilingGit
Copy link

Some of the entries don't export properly and have a key of null, so it is best to actually go through all the accounts and make sure you can login.

OK, thank you for the clarification, and have a nice day!

@PsySc0rpi0n
Copy link

I have just tried this method using Authy 2.2.3 (downloaded from the comments here) and Debian Bookworm and Firefox 115.5.0esr and from more than 20 QR codes scanned, only one was added to Aegis authenticator successfully. All other codes returned the following error:

An error occurred while trying to read the QR code.
com.beemdevelopment.aegis.otp.GoogleAuthInfoException: Bad secret (java.lang.IllegalArgumentException: com.google.common.io.BaseEncoding$DecodingException: Invalid input length 9)

@Kofl
Copy link

Kofl commented Dec 28, 2023

Worked fine for 2FAS, only the QR codes for importing fail which are tied natively to Authy like Twitch

@l0wBoB
Copy link

l0wBoB commented Dec 28, 2023

I couldn't get it to work with Edge 121.0.2277.4 or Chrome 120.0.6099.130. :(
Uncaught ReferenceError: appManager is not defined at Snippet#1:14:12

@joshjohanning
Copy link

This still worked. The only issue was I forgot to decrypt vault after logging in (my Authy backup password), so I was getting null for decryptedSeed and was confused. After logging in AND entering in my vault password, then it worked.

  1. Download DMG

macOS: https://pkg.authy.com/authy/stable/2.2.3/darwin/x64/Authy%20Desktop-2.2.3.dmg

  1. disabled auto update (before i ran this i opened the app, turned off wifi, got to the login step and turned back on wifi, closed and re-ran this command (but hopefully you could just run this command from the getgo)
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
  1. Open authy:
open -a "Authy Desktop" --args --remote-debugging-port=5858
  1. Open localhost:5858

  2. Run this in the terminal

appManager.getModel().forEach(i => console.log(i))
  1. Open up the line object you are looking for and find the decryptedSeed.

@MatheusVp2
Copy link

Do you know if there is a tutorial on YouTube with step by step instructions on how to do this export?

If anyone can do it, I would appreciate it

@navels
Copy link

navels commented Dec 29, 2023

@l0wBoB

I couldn't get it to work with Edge 121.0.2277.4 or Chrome 120.0.6099.130. :( Uncaught ReferenceError: appManager is not defined at Snippet#1:14:12

The browser instance at http://localhost:5858/ is already running dev tools, are you opening another dev tools window? That would cause this error.

@l0wBoB
Copy link

l0wBoB commented Dec 29, 2023

I couldn't get it to work with Edge 121.0.2277.4 or Chrome 120.0.6099.130. :( Uncaught ReferenceError: appManager is not defined at Snippet#1:14:12

The browser instance at http://localhost:5858 is already running dev tools, are you opening another dev tools window? That would cause this error.

@navels
Thank you!

@SlyAceZeta
Copy link

SlyAceZeta commented Jan 5, 2024

Thank you for putting together this resource! It was extraordinarily helpful for me; I was moving from Authy to Aegis manually site-by-site until Discord prevented me from changing my 2FA settings, so without root access this was the perfect solution. I thought I needed Chrome (even went so far as to install it) before realizing @navels was right and the instructions were referring to the dev tools inside the page rather than the browser's dev tools. The one oddity I noticed from running this script, both in Chrome and Firefox, was that the QR codes doubled-up. No big deal, just needed more scrolling. I did also need to restore Update.exe by "reinstalling" Authy so I could fully delete it from the Windows programs list, but now I'm free of Authy's clutches. Thank you again!

@nemideia
Copy link

nemideia commented Jan 8, 2024

Thx a lot. You helped me to finally migrate all my accounts MFA to bitwarden.

@cjmaxik
Copy link

cjmaxik commented Jan 8, 2024

Does not work with 2FAS anymore.

This QR code does not work!

@tilsgee
Copy link

tilsgee commented Jan 9, 2024

I couldn't get it to work with Edge 121.0.2277.4 or Chrome 120.0.6099.130. :( Uncaught ReferenceError: appManager is not defined at Snippet#1:14:12

same

@navels
Copy link

navels commented Jan 9, 2024

I couldn't get it to work with Edge 121.0.2277.4 or Chrome 120.0.6099.130. :( Uncaught ReferenceError: appManager is not defined at Snippet#1:14:12

same

see my reply here: https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93?permalink_comment_id=4811653#gistcomment-4811653

@amitsax
Copy link

amitsax commented Jan 9, 2024

https://support.authy.com/hc/en-us/articles/17592416719003-Authy-for-Desktop-End-of-Life-EOL-
Authy for Desktop End of Life (EOL)
The Authy Desktop apps for Windows and MacOS that are available or were previously downloaded from authy.com/download as well as those for Linux will reach their End-of-Life in August #2024.

@kdorland
Copy link

kdorland commented Jan 9, 2024

https://aur.archlinux.org/packages/authy-export-git https://github.com/alexzorin/authy this one is working fine for me in (may 2023)

Thanks! Still working January 2024.

@peliopoulos
Copy link

Thanks so much @gboudreau! Couldn't find an older version of Authy for linux, but the Windows instructions worked perfectly with Edge.

@ruario
Copy link

ruario commented Jan 10, 2024

@peliopoulos FWIW

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.

@kabforks
Copy link

This worked as of 11.01.2024. However, Authy is very quick to auto update. Shortly after opening the old version of Authy, it would try and auto update... this caused my DevTool to become unresponsive. And I would have to reinstall the old version.

In order to get this to work, I would have to run the snippet very, very fast after opening Authy (you just have 1-10 seconds).

I just put everything in Bitwarden and can reexport later.

@thejae
Copy link

thejae commented Jan 12, 2024

This worked as of 11.01.2024. However, Authy is very quick to auto update. Shortly after opening the old version of Authy, it would try and auto update... this caused my DevTool to become unresponsive. And I would have to reinstall the old version.

In order to get this to work, I would have to run the snippet very, very fast after opening Authy (you just have 1-10 seconds).

I just put everything in Bitwarden and can reexport later.

Just go into your authy installation directory and delete the updater executable :)

@Goldmaster
Copy link

If you want to import JSON backup file using this method into aegis, then use the Bitwarden json import option, then import the tokens.

@peliopoulos
Copy link

peliopoulos commented Jan 12, 2024

Here's some more detailed instructions to prevent Authy for Windows from updating. Tested using Win10

  1. Install Authy 2.2.3, Authy will then launch, exit it immediately.
  2. in the Users/XXXX/AppData/Local/authy folder, delete Update.exe
  3. Delete the app-2.4.2 folder if it exists (or newer if there is a newer one)
  4. In the app-2.2.3 subfolder, delete Update.exe

I'm not sure if all of these steps are needed, but these did work for me today.

EDIT: When I tried to scan the QR code in Aegis, they appeared to scan in correctly. However, the codes that Aegis generated did not work. I followed @Goldmaster 's recommendation to use the Bitwarden json import option and it worked. Just note that the window that popped up to save the json file was hidden behind other windows (I used Edge FYI). I imported the file into Aegis and was successul using the "Import from file" command, then selecting "Bitwarden".

@djsiropchik
Copy link

Does not work with 2FAS anymore.

This QR code does not work!

It's working very well. Easy scanned all codes and it's the same codes like in Authy.

@HaleTom
Copy link

HaleTom commented Jan 13, 2024

To migrate Authy to 2FAS, I went via Google Authenticator for myself and the there was some whitespace issue in the comments field where my email and the service names were elided.

Using LastPass as intermediary (thence export to JSON) worked well, but sometime the "Service/Site" field was empty - just like it was in Authy.

@netcrystals
Copy link

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?

Thanks.
Regards,
Equi

@japzone1
Copy link

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?

Thanks. Regards, Equi

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.

@djsiropchik
Copy link

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?
Thanks. Regards, Equi

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.

Still not clear. But if I don't use special something. Twitch can support all types of 2fa apps I guess, so I'll not lost my 2fa.

@gboudreau
Copy link
Author

gboudreau commented Jan 13, 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...)
image

@djsiropchik
Copy link

So if all my 2fa codes are 6 digits I'll not have any problems

@physx2494
Copy link

You can also identify these special apps when you add their tokens on Aegis.

image

Notice that they are sorted at the top and have timers that run differently (and faster) than standard tokens.

@stephan1827
Copy link

When I run the script I get 2D bar codes and I can import the first one successful, but when I try to scan the 2ns with 2FAS I get an error message that the code already exists.

@physx2494
Copy link

Because the 2nd QR is the duplicate.

@stephan1827
Copy link

No its not the second bar code, its any of the following barcodes

@Instinctlol
Copy link

Also worked with Firefox, no problems. Many thanks :)

@loboaureo
Copy link

On todays date, still works, if you do in windows and have a problem running this version, you can go to the folder, make a link and add the --remote-debugging-port=5858 to it

imagen

Its an easy way to made a keepass library with the json? or i should go to bitwarden, import, then export and import this to keepass?

@designworld-ca
Copy link

Got this working on Windows 11 with Brave.
To prepare:
After installing the older Authy version delete or rename all copies of updater.exe in the Authy folder.
You cannot have any other browser windows open when running this.
Had to wrestle with my firewall when I found http://localhost:5858 gave ERR: Connection refused as I found it had tried to help and deleted 127.0.0.1 from the hosts file

Just using the first script gave the "Uncaught ReferenceError: appManager is not defined at Snippet#1:14:12" but when I used the script with the export to JSON it worked with no duplicates.

Thank you gboudreau!

@bigmo-git
Copy link

Thank you so much for this.
Followed your instructions and worked perfectly on MacOs
I only had to enter my backup password on Authy and voila!
What would be even better is if the first snippet would also export the QR codes to a file. I tried uncommenting the last line but it didn't generate a file with the QRs (console.save(data, 'authy_backup.json')

Thanks a million gboudreau

@mcbrineellis
Copy link

Thank you so much! Terrifically documented, worked great and I was able to transfer my codes over to 1Password :)

@AndyZaa
Copy link

AndyZaa commented Jan 25, 2024

Echoing a Thank You for this great code and excellent documentation! I was able to migrate 41 Authy accounts to 2FAS by using the first method to display the tokens and scan them in quickly. The only one that failed is a Gemini account which is a 7-code 2FA. You are wonderful, @gboudreau !!! :)

@agreedSkiing
Copy link

agreedSkiing commented Jan 27, 2024

No its not the second bar code, its any of the following barcodes

So it seems like the procedure is not a 100% since all of the QR codes that are generated on my desktop (Manjaro with kernel 6.6.10-1) are being created with ?secret=null with both methods except for the special authy codes.

Update: When trying to print the i object all my i.secretSeed are null and as they should the i.encryptedSeed are salted

Update 2: Forgot to unlock my password vault with the backup password...

Update 3: If you use a later version of authy and don't want to execute the scripts found in the main post, then the comment from https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93?permalink_comment_id=4710236#gistcomment-4710236 worked for me with current latest version of authy, but i requires npm to be installed on your device and for flatpak my path was

mdkir -p /tmp/authy
npx @electron/asar command -v extract /var/lib/flatpak/app/com.authy.Authy/x86_64/stable/<UUID>/files/extra/authy/resources/app.asar /tmp/authy

@brenc
Copy link

brenc commented Jan 31, 2024

I am so disgusted with how hard Authy has made this process. Thanks to OP and the commenters here I was able to migrate 80 (!!) 2fa accounts from Authy to 2FAS Auth. This would have been such a pain otherwise.

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

Use OP's instructions but use this snippet instead. It'll prompt you to save a json file. Securely copy that to the device on which you'll do the import. Then within 2FAS Auth go to settings, import tokens, Raivo OTP, select the exported json (or select import right from the main screen on a fresh install). Make sure you delete the json when done importing.

Tested on iOS and Android. Notes:

  • All normal 2fa accounts appear to work. I compared several in 2FAS and Authy and they were the same code. Plus I've used a few codes directly from 2FAS and they all have worked. I'm going to keep Authy around for a while to make sure everything is good before deleting anything though.
  • Authy accounts won't import on Android. I have no clue why. Doesn't bother me because I have just five Authy accounts and I will be getting rid of them soon.
  • On iOS 2FAS will import the Authy accounts but the codes don't work. They don't work direct from the Authy app on the desktop either so I don't know what's up with that.

@kbonnel
Copy link

kbonnel commented Feb 7, 2024

Does not work with 2FAS anymore.

This QR code does not work!

I did it today using 2FAS and everything worked. I compared each TOTP between the two and they were the same for me.

@Kinuseka
Copy link

Kinuseka commented Feb 8, 2024

Works for me! 02/08/2024

Do note that you need to first decrypt your authy account before doing this! the secret returns none if you dont do that and will always return the same code for everything! That was a mistake I made that took me awhile to figure out.

But this method still works and I successfully moved from authy to aegis.

@xflyboy
Copy link

xflyboy commented Feb 10, 2024

Thanks to all OP and @brenc. And clarification from @Kinuseka.
Once Decripted all backed up accounts were able to import JSON file into Android 2FAS app.

@dvshkn
Copy link

dvshkn commented Feb 11, 2024

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. On phone in Aegis use Settings > Import & Export > Import from file and select Aegis file format to import the JSON file.

I spot checked the results in Aegis on a few accounts and the codes generated were the same as Authy. I only tested with simple 6 digit codes.

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

@JKaique01
Copy link

Followed the exact instructions at 11.feb.2024 and they worked perfectly. Thank you

@1icolo
Copy link

1icolo commented Feb 12, 2024

Gemini

We have the same issue with Gemini

@LightningManGTS
Copy link

LightningManGTS commented Feb 13, 2024

I may be dumb but I keep getting appManager is not defined when running snippet (either version) Am I missing something?

Edit: Nevermind. You don't need to manually open the dev tools window, you use the one that already shows up in the window.

@Artanisx
Copy link

Just confirming this works! Thanks :)

@NeloBlivion
Copy link

Just saw Authy was sunsetting their desktop app and immediately exported to 2FAS with brenc's script 👍
On Authy, only one account (Twitch) shows under "Twilio Authy Accounts". Does that mean i can safely delete my Authy account after unbinding it from Twitch, and everything else will be fine?
Thanks!

@markd89
Copy link

markd89 commented Feb 13, 2024

I have been using Authy under Windows and want to migrate it to something that is Open Source and also Windows. I am using only the 6 digit OTPs. Any suggestions of what app to use?

@sahal
Copy link

sahal commented Feb 13, 2024

You mention Linux, macOS, and Windows. For macOS and Windows, you link directly to the version. You don't mention how to pull the Linux snap at a specific version.

Based on this commit to Arch's user repo, AUR, the snap url for 2.2.3 is https://api.snapcraft.io/api/v1/snaps/download/H8ZpNgIoPyvmkgxOWw5MSzsXK1wRZiHn_18.snap

I just installed this version of the Authy snap using the AUR PKGBUILD file. I pulled the git repo (https://aur.archlinux.org/authy.git) at the 2.2.3 commit hash (9ce65fbd76fef54c3afbe3afde9b9e9804c4a6ad). Then I used makepkg to build and install the file using the instructions on the Arch wiki.

From there I was able to continue with the instructions listed for Linux.

@DePingus
Copy link

In case anyone is looking for Linux instructions, you can install the old 2.2.3 version of Authy with Flatpak.

  • First install the current version
    flatpak install flathub com.authy.Authy
  • Then find the v2.2.3 commit
    flatpak remote-info --log flathub com.authy.Authy
  • Update to the old version
    sudo flatpak update --commit=83c0df0dd48bbb6ad851f5cc62d6e0836e56e499c7a79041241809f8296e65cc com.authy.Authy
  • In order to get the json file out you have to give Authy's flatpak environment access to your Home folders. You can do this with the Flatseal app or in the terminal
    sudo flatpak override --filesystem=home com.authy.Authy

If you're going to 2FAS, the easiest way I found was to import into Aegis first (using the Bitwarden file option), then exporting an unencrypted json from Aegis. Finally using that new json to import into 2FAS (using the Aegis option).

@livejamie
Copy link

This worked flawlessly, you can import that JSON file into so many different programs. Thank you!

@idlepickle
Copy link

Thanks for this! Just migrated everything over to 1Password. Well, all except damned Gemini

@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

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!

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