Update 04.04.2020: Please take a look at many of the forks of this gist or comments, where people have updated or improved upon the code. I have not needed this in a long time, which is why the original document has not been updated and the code probably does not work. Stay secure and only copy and paste code that you trust.
There is an increasing count of applications which use Authy for two-factor authentication. However many users who aren't using Authy, have their own authenticator setup up already and do not wish to use two applications for generating passwords.
Since I use 1Password for all of my password storing/generating needs, I was looking for a solution to use Authy passwords on that. I couldn't find any completely working solutions, however I stumbled upon a gist by Brian Hartvigsen. His post had a neat code with it to generate QR codes (beware, through Google) for you to use on your favorite authenticator.
His method is to extract the secret keys using Authy's Google Chrome app via Developer Tools. If this was not possible, I guess people would be reverse engineering the Android app or something like that. But when I tried that code, nothing appeared on the screen. My guess is that Brian used the code to extract the keys that weren't necessarily tied to Authy.
I had to adapt the code a little and you can see the result below, but here's what I discovered about Authy's method:
- They use the exact same algorithm to generate passwords as Google Authenticator and similar (TOTP)
- The passwords are one digit longer - 7 digits (usually they're 6, with exceptions), but if you've looked at one of the Authy generated passwords already, you probably noticed it too
- The password validity period is 10 seconds (instead of usual 30). Authy shows 20 seconds, but that means a slightly different thing. Don't substitute this period longer in your Authenticator.
- Authy's secret keys are in hex already, so they need to be turned back to base32 for working QR codes
So as long as you have an authenticator which can do longer passwords than 6 characters and do custom time periods, then congratulations, you can use the following method. If you are not sure, scan this code with your authenticator to test. Don't forget to delete it afterwards. The code should have 7 digits and should change every 10 seconds.
Known to work:
- 1Password for OS X
- 1Password for iOS
- Google Authenticator
Known not to work:
- 1Password for Windows (doesn't support other digit counts and timeouts yet)
- Authy for iOS (doesn't support other timeouts than 30s, the irony!)
This method has only one gotcha - if you want add a new service that relies on Authy, you will need to run Authy again. I am assuming you know how to use Authy and have some services added already. You can probably get rid of Authy on your phone and log in to Authy on your Chrome app using SMS or keep it permanently disabled under your extensions once you have logged in. In that case set a master password for Authy, stay secure.
- Install Authy from Chrome Web Store
- Open Authy and log in, so you can see the codes being generated for you
- Go to Extensions page in your browser (
chrome://extensions/
or Menu -> More tools -> Extensions) - Tick developer mode in top right corner
- Find Authy from the list and then click on
main.html
- Chrome developer tools with Console selected should open. If it didn't, go to Console tab.
- Paste following and press enter:
/* base32 */
/*
Copyright (c) 2011, Chris Umbel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
function quintetCount(buff) {
var quintets = Math.floor(buff.length / 5);
return buff.length % 5 == 0 ? quintets: quintets + 1;
}
encode = function(plain) {
var i = 0;
var j = 0;
var shiftIndex = 0;
var digit = 0;
var encoded = new Array(quintetCount(plain) * 8);
/* byte by byte isn't as pretty as quintet by quintet but tests a bit
faster. will have to revisit. */
while(i < plain.length) {
var current = plain[i];
if(shiftIndex > 3) {
digit = current & (0xff >> shiftIndex);
shiftIndex = (shiftIndex + 5) % 8;
digit = (digit << shiftIndex) | ((i + 1 < plain.length) ?
plain[i + 1] : 0) >> (8 - shiftIndex);
i++;
} else {
digit = (current >> (8 - (shiftIndex + 5))) & 0x1f;
shiftIndex = (shiftIndex + 5) % 8;
if(shiftIndex == 0) i++;
}
encoded[j] = charTable.charAt(digit);
j++;
}
return encoded.join('');
};
/* base32 end */
function hexToInt(str) {
var result = [];
for (var i = 0; i < str.length; i += 2) {
result.push(parseInt(str.substr(i, 2), 16));
}
return result;
}
function hexToB32(str) {
return encode(hexToInt(str));
}
window.open('data:text/html;charset=utf-8,' + encodeURIComponent('<!DOCTYPE html>'+ '<html lang="en">'+ '<head><title>Embedded Window</title></head>'+ '<body>' +
jQuery(require("models/apps/app_manager").get().getDecryptedApps()).map(function (ndx, elem) {
if (!elem.secretSeed) { return }
var name = elem.name || elem.originalName
return "<h1><img src='" +
require('models/assets/asset_manager').get().assetAccounts[elem.assetsGroup].menuItemUrl +
"'>" + name + "</h1><h2>" + hexToB32(elem.secretSeed) + "</h2>" +
"<img src='https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/" + encodeURI(name) + "%3Fsecret%3D" + hexToB32(elem.secretSeed) + "%26digits%3D7%26period%3D10'/>"
}).toArray().join("<br>")
+ '</body>'+ '</html>' ) );
- A page should open with QR codes for all of your entries, scan them in!
- Close opened window and developer tools.
- Disable Authy app on Chrome or remove it
- Disable Developer mode
- Brian's gist
- JavaScript implementation of base32 encoding by Chris Umbel, et al.
- Google Authenticator URI format
- I am not responsible for your actions.
- I am sure someone has already discovered everything I wrote before, but I couldn't find anything written about it in detail, I didn't invent anything new here
- The code is a horrible hack, it works for what it does and that's the important bit, improvements are welcome
- If anyone from Authy reads this - security shouldn't rely on obfuscation or hiding of any sort and should take advantage of freedom of choice where possible. I love the idea of the keys being tied to ones phone number and making this system easy to use for everyone, but please make these URI-s exportable to other applications if users wish to do so - it's possible as demonstrated above and you probably know it. Transparency is what makes this system secure. If you don't wish to do that, then please don't break this method of acquiring keys.
I had the same problem, @Miladiir. This code hasn't been updated in 18 months, probably broke when Authy released a new version. Rather than debug it, I just used a simple snippet for a console URI dump that I found here: https://gist.github.com/tresni/83b9181588c7393f6853#gistcomment-2024680 That worked perfectly for getting the secrets into 1Password's TOTP fields.