Cracking My Encrypted Authy Backups

So the other day I opened Authy which I use on Android in place of Google Authenticator for two factor auth. Authy backs up your authentication token keys to their server after encrypting them, which is pretty sweet in case you drop your phone in your beer. However, somehow authy decided it was going to forget all my token keys (I know right? I have no idea why, karma maybe.) and that I needed to recover them from the backup. Ok, that’s cool, enter my password, incorrect. Oh crap, try again… incorrect. Incorrect. Incorrect.

This goes on for some time.

Clearly when setting up my backup password I fat fingered it or something (I wasn’t really paying much attention when I did it). Ok, looks like I’m going to have to crack my own authy backup. Could be fun (or so I told myself between fits of rage and uncontrollable sobbing).

EDIT: Sooo, a lot of you guys are freaking out over Authy’s encryption. Authy’s backup encryption is reasonable and does not have any issues of concern that I noticed. This post is just about how I recovered my password, it’s not about flaws in Authy.

Step 1 – Intercept the Encrypted Auth Token Keys

First of all we have to be able to intercept traffic in clear text between the authy app, and authy’s servers. Authy uses HTTPS but doesn’t do any kind of certificate pinning (yay for me) which means I can install my own trusted CA certificate on my device which essentially lets me “man in the middle” the connection and sniff traffic without upsetting the authy app. I’m not going to go into detail here, but I used the awesome mitmproxy as described on the site and it worked a treat.

Once that was working, I cleared all the data from the authy app so it thought it was newly installed and went through the authenticate and phone reset process. At some point authy goes and downloads the encrypted tokens from their servers, which looks like this:

"authenticator_tokens": [
 {
 "account_type": "gmail", 
 "encrypted_seed": "UZNL+UPVK+5RYUoBlhkuQzCcOb3nMdYxKGFmHEZ4TMxTX7MOPJUayQAMDVJ", 
 "name": "My Gmail Account", 
 "original_name": "Google:someaccount@gmail.com", 
 "salt": "N2QqyJrn7cOHBqTY32uY1cL4IHCANm", 
 "unique_id": "0000000000"
 }, 
 {
 ........ and so on and so forth

Really all we need is the encrypted_seed and salt value. Step one done. If my phone was rooted, I suspect I could pull this info off the file system somehow, but it's not.

Step 2 - Write Some Cracking Code

So now I have something to crack, lets work out how this stuff is encrypted. Authy were nice enough to provide a blog post which gives pretty much all the detail needed. Essentially they take your password and PBKDF2 it using SHA-256, 1000 rounds, and a unique salt. Then they use this derived key to encrypt your token using AES-256 in CBC mode with PKCS#5 padding. Which is all fine and good, but they fail to mention how they get their IV (required by CBC mode) but because of this glaring omission I guessed it'll be something silly like all zeros which is what people choose when they don't really care about an IV (turned out I was right). Edit: does a static IV of all zeros matter in this specific case? Probably not. See my comment below for more details.

Alright, lets write some code! A quick peak at the authy APK (no need to decompile) showed some references to spongycastle (which is bouncycastle with some minor changes for android) I decided to use bouncycastle's "standard" way of doing the above which gave me this hacked up piece of hacky hack.

import java.security.Security;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

public class AuthyDecryptor {

    private IvParameterSpec ivSpec;
    private byte[] cipherTextBytes, saltBytes;
    private Cipher cipher;

    static {
        Security.addProvider(new BouncyCastleProvider());

    }

    public AuthyDecryptor(String cipherText, String salt) {
        byte[] ivBytes = new byte[16];
        Arrays.fill(ivBytes, (byte) 0);
        ivSpec = new IvParameterSpec(ivBytes);
        cipherTextBytes = Base64.decode(cipherText);
        saltBytes = salt.getBytes();
        cipher = null;
        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.exit(2);
        }
    }

    public String decrypt(String password) {

        try {
            PKCS5S2ParametersGenerator paramGenerator = new PKCS5S2ParametersGenerator();
            byte[] pwdBytes = PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password.toCharArray());
            paramGenerator.init(pwdBytes, saltBytes, 1000);
            SecretKeySpec secretKeySpec = new SecretKeySpec(((KeyParameter) paramGenerator.generateDerivedParameters(256)).getKey(), "AES");

            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
            byte[] b = cipher.doFinal(cipherTextBytes);
            return new String(b);

        } catch (BadPaddingException ex) {
            return null;
        } catch (Exception ex) {
            return ex.getMessage();
        }
    }

}

If you know java (everyone knows java right?) use of the class above should be pretty straight forward. The decrypt method will return null when decryption fails, or a string otherwise.

Step 3 – Bruteforce that Mother

At this point I know what my password “should” be, but it’s not. So after writing some more code to turn the class above into a command line tool I tried every password I could think of, no luck damnit! Ok, lets go to plan B.

Enter the wonderful cracking tool John the Ripper (at which point I thought, surely there’s a tool that will bruteforce PBKDF2-AES256-CBC-PKCS5 without me writing any code, oh well, too late now). Anyways… after creating a word list of every password it could be, I then used john to apply the jumbo mangling rules and piped these generated passwords to my cracker. I saved the output to a file, which promptly started filling up with useless garbage. Hmm. Turns out my decrypt method will return null most of the time, but a lot of the time returns binary crap. Oh well, I figure I can filter that later.

Some time passes (I don’t know how long, I was getting about 2000 password tries per second on my laptop using all cores. Not so fast really, but fast enough).

All passwords tries are complete, now I just have to mine the output file and hope I find gold. While I’m not certain exactly what a successful decryption looks like, I can guess it’ll have a base32 encoded string that’s 16 characters long in it (cheers wikipedia). A quick grep (grep -E ‘[[:alnum:]]{16}’ out.txt) of the file and BAM there’s my password (at this point there was much loud happiness)! Turns out it was what it “should” have been, but missing a single digit in the middle, which I never would have guessed manually (I would have gotten bored and hurled the phone against the wall way before guessing that).

Step 4 – Profit

Now it was just a matter of entering the correct password into authy and low and behold I have access to all my tokens (and accounts) again.

Job done, time for a beer.

9 thoughts on “Cracking My Encrypted Authy Backups

  1. All zero IV’s lol. I just dont get it how apps like these cant get it right and then they call themselves “Authy”
    Also, I am guessing they weren’t using “Authenticated” encryption, right? I didnt see any mention of HMAC anywhere.

    • The 0 IV is an interesting one. They’re using a unique salt per encrypted token which means a unique AES key, which means that if you were to get the application to encrypt the same plain text twice you would get a completely different cipher text which is one of the main reasons to have a unique IV (*mumbles something about chosen plain text attacks*). However there may be a weakness hiding in there due to it. If I was implementing it I’d probably generate a random unique IV for each token just out of paranoia, but I don’t see it as a big deal. Edit: In summary, can I steal your tokens because of it? No.

      Authenticated encryption would be good if you cared if someone can mess with the ciphertext to achieve something. In this case you’d have issues as you’d have to hack authy’s server or break the SSL/TLS connection and mess with the ciphertext in transit (unlikely). After which you’d do… what? I’m not sure the value of authenticated encryption vs the implementation complexity in this case.

  2. @duke snarkenton: What I don’t get is that you are smart enough to notice it’s his own wordlist but dumb enough to miss the bigger picture.

    @aj: Thanks for putting in the time to document your process. I thought it was an interesting read as well. It’s good to understand how an app like Authy handles it’s encryption.

Would you like to write a non-snarky comment?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s