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:
- When you sign up, a key gets generated client-side (in your browser)
- You choose a password
- The key gets encrypted with your password
- The encrypted key is sent to ProtonMail
- Incoming email is encrypted (using your public key) by ProtonMail before writing anything to disk
- 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 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
- Log out
- Open the devtools, go to the network tab
- Enter your username and password, submit the form
- Check the response of the call to
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.
- Copy the whole response object, open an incognito window, navigate to ProtonMail login screen (
https://mail.protonmail.com/loginbecause 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.)
- Now we'll save the response to a variable:
res = $_;and retrieve the private key with
res.PrivateKeyand save it to a file (e.g.
- Save the salt to some variable for convenience:
salt = res.KeySalt;
- 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:
- https://github.com/ProtonMail/WebClient/blob/a605dc3dbbd95678b8c3005486951962dd65588c/src/app/authentication/services/passwords.js#L4-L14 (opens new window)
- https://github.com/ProtonMail/WebClient/blob/a605dc3dbbd95678b8c3005486951962dd65588c/src/app/authentication/services/passwords.js#L29-L43 (opens new window)
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)) );
Now save this passphrase.
Decrypting my email
I saved the encrypted email with its PGP header and footer to a file I named
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: "email@example.com <firstname.lastname@example.org>" 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
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 "email@example.com <firstname.lastname@example.org>" Hey!<br /><br />THE REST OF MY EMAIL
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.