tools May 18, 2017

Retrieving my Private Key from ProtonMail

Now this is the story all about how My mail got coded, turned upside down And I'd like to take a minute just sit right there I'll tell you how I recovered the private key from ProtonMail.

My primary email provider is ProtonMail. It's an end-to-end encrypted email provider. This means ProtonMail cannot read my emails: they are stored encrypted and ProtonMail doesn't have the key to decrypt them.

End-to-End Encryption on ProtonMail

It roughly works like this:

  1. When you sign up, a key gets generated client-side (in your browser)
  2. You choose a password
  3. The key gets encrypted with your password
  4. The encrypted key is sent to ProtonMail
  5. Incoming email is encrypted (using your public key) by ProtonMail before writing anything to disk
  6. When you sign in on protonmail.com or in the mobile app to read your email, you receive your encrypted private key and the encrypted emails, the private key gets decrypted using your password (which never gets sent to their server), the decrypted private key is used to decrypt your emails

An Email Failure

Last week I received an email from a good friend who's been traveling a lot lately and who doesn't get access to Internet that often. I took advantage of a long subway ride to write a lengthy email back. I hit "Send" and thought he would get it.

One week later, he replied something like "Excuse me?" with the encrypted version of the email I sent him. I don't know why this happened, my best guess is that the intermittent 4G connection I was getting from my subway car interrupted the communication between my mobile phone and ProtonMail servers which resulted in this. I was sending this email to an email address outside of ProtonMail which means that my friend would receive a cleartext version of this email while my mail box only keeps encrypted versions of an email.

The email that was sent encrypted instead of in cleartext

The first thing that came to mind was to look for my private key to decrypt this email myself, and send it again. After searching in ProtonMail FAQ, it seemed exporting your private key isn't supported.

Here's how I did it anyway:

ProtonMail Private Key and its Passphrase

  1. Log out
  2. Open the devtools, go to the network tab
  3. Enter your username and password, submit the form
  4. Check the response of the call to api/auth

What we can get from the auth API response

From this response we can retrieve the private key and a salt. You might think "I'll import the private key in GPG and use my mailbox password as passphrase". That's smart but that would be too easy. The mailbox password isn't the passphrase for the private key we retrieved. To get the passphrase, we'll need the mailbox password and the salt.

  1. Copy the whole response object, open an incognito window, navigate to ProtonMail login screen (https://mail.protonmail.com/login because it got protonmail's JS loaded), open the Console, paste the whole object. (The reason I did this in an incognito window is to prevent this data from staying in my console history.)
  2. Now we'll save the response to a variable: res = $_; and retrieve the private key with res.PrivateKey and save it to a file (e.g. privkey).
  3. Save the salt to some variable for convenience: salt = res.KeySalt;
  4. Also save your mailbox password in some variable, we'll need it in a few: mailboxPassword = 'correct horse battery staple';

ProtonMail being open source, I looked at the source to figure out how to decrypt the private key to be able to use it with GnuPG on my own. The two relevant parts I found are here:

I did a tiny bit of refactoring to get rid of the promises, here's the result:

// Fiddle with the `salt` we assigned earlier
saltBin = pmcrypto.binaryStringToArray(pmcrypto.decode_base64(salt));
// Tiny wrapper around bcrypt, useless but eh
bcrypt = (str, salt, cb) => dcodeIO.bcrypt.hash(str, salt, cb);
// Get the actual passphrase!
bcrypt(
  mailboxPassword,
  '$2y$10$' + dcodeIO.bcrypt.encodeBase64(saltBin, 16),
  (err, hash) => console.log(hash.slice(29))
);
1
2
3
4
5
6
7
8
9
10

Now save this passphrase.

Decrypting my email

I saved the encrypted email with its PGP header and footer to a file I named email. I'll use gnupg (brew install gnupg).

Import the private key using gnupg. This should open a GUI asking for your passphrase, the one we just got at the previous step:

❯ gpg --import privkey
gpg: key 98F7B31F74FC09D3: "me@example.com <me@example.com>" not changed
gpg: key 98F7B31F74FC09D3: secret key imported
gpg: Total number processed: 1
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
1
2
3
4
5
6
7

Last step, decrypt the email using gnupg. This should again open a GUI asking for the same passphrase as before:

❯ gpg -d email
gpg: encrypted with 4096-bit RSA key, ID 98F7B31F74FC09D3, created 2016-10-11
      "me@example.com <me@example.com>"
Hey!<br /><br />THE REST OF MY EMAIL
1
2
3
4

That was fun. I kind of understand why ProtonMail wouldn't want the private keys to be too easily exportable. I'm a happy customer and don't want to comment on that.

#security #privacy

Follow me on Twitter, GitHub.

unless otherwise stated, all content & design © copyright 2021 victor felder