ProtonMail / WebClients

Monorepo hosting the proton web clients
GNU General Public License v3.0
4.25k stars 541 forks source link

Security question about key exchange / Wrong model? #38

Closed ffries closed 2 years ago

ffries commented 7 years ago

Hello,

Your website states: https://protonmail.com/support/knowledge-base/how-is-the-private-key-stored/


I don't understand Protonmail cypto model:

Client public key is encrypted with password. It is sent encrypted to server. Thus private key could be stolen (1) on server (2) during transmission (3) on client. Password should not be weak and you don't test password weakness. It is vulnarable to back-doors attacks.

GnuPG relies on a slightly different model. In theory, GPG private key should be created and stored on client. In the client keyring, a client GPG key is created from the public GPG key. Client encrypts message with GPG public key. Message is sent encrypted to server. So email server sees only encrypted traffic. If public key is stolen, it cannot be used to decrypt data, but only encrypt data. Furthermore, there is not private key (encrypted or not) on server. So it cannot be stolen on server.

So a better model would be (a) private GnuPG key kept secretly on client side and (b) only public GnuPG key sent to server for encryption of messages.

Could you correct my message and explain a little bit more?

Furthermore (but don't answer), when you create a private key, we are not sure that is part of a bigger keyring with a master GPG key, which would allow to decrypt all traffic. We can only trust you are good guys. Also you may be vulnerable to man-in-the-middle attacks.

Personally, I would prefer a knowledge base showing also possible weakness, not only trying to convince people in using Protonmail, saying it is "rock-secure".

I will try and use Protonmail for fun, but will not recommend it serious clients unless you explain better the encryption protocol. I must be wrong somewhere, please correct me.

remyrd commented 7 years ago

Correct me if I'm wrong: I think what you're missing is that the client's private key is kept encrypted in the server and during the transmission. So: (a) it is encrypted on the server (even generated for each login I believe), keeping it on clients would be a nightmare to keep track of. (b) server already has public key, that's the point, so that other people can just request the server to encrypt messages directed to you

Hope I clarified, at least it is the way I understand things to work. Cheers

bartbutler commented 7 years ago

So, the original question is very confused. There is something wrong with almost all statements, including those about how GPG works, so rather than attempt to correct it point-by-point I will explain briefly what we do and the differences re: GPG.

For ProtonMail, the client generates your key pair when you make your account. The private key is encrypted with a password only your know. These keys are then sent to the server. The server cannot decrypt or use the private key because it does not know the passphrase. The server furnishes the private key to your client on login, you decrypt it locally, and you read your mail locally. Storing the private key on the server in encrypted form allows us to provision multiple clients (mobile, web, etc.) with the same set of key automatically, in particular thin clients like web which do not have a 'history'. The public key is, as it's name implies, public and is furnished to people who want to send you mail. This latter process could be abused by a malicious server, and we are working on that. That said, GPG's answer to this problem (Web of Trust, key-signing parties, etc.) are not very satisfactory either. It's a very old problem with public key cryptography.

I am simplifying the passphrase part slightly--in reality we use a slow password hash function to generate a hash, and encrypt the key with that to make offline brute-forcing slow and the encrypted key alone less sensitive, but this detail does not affect the general scheme.

GPG is similar except that the keys are kept and managed locally, and key discovery is done via keyserver or manually rather than automatically. I have no idea what is meant by having a GPG master key that can decrypt everything--I have never heard of that and am pretty certain that does not exist.

Hope that helps!

ffries commented 7 years ago

Thank for the explanation.

I understand now the process and why the private key is stored encrypted on the server.

To me it is a potential leak, which makes ProtonMail only half-secure. ProtonMail is resistant to malicious attacks, but not to governments and you should not pretend that it is. Governments have backdoor access to at least Android, Windows and iOS and probably GNU/Linux too. The attack surface is quite large, as governments may use SSL tunnels and can inject code on the fly. This can even be done at hardware level, as modern computers have a secondary chip inside the main CPU able to do so. So requesting a password typed on a personal computer is possible.

And thanks god, looking at what arrived today in Manchester, it is good they do have backdoor access.

The only way for you to obtain some level security is to ask clients to use a security hardware. You should define your own security hardware, with a minimal attack surface, i.e. something like a security chip. Then produce it in your own premises to make sure there are not modifications. And ask the security hardware to do the crypto job on the client side.

It can be done, but it is still a long way.

But please don't pretend that ProtonMail can resist governments. First, it is not good to resist governments and laws. Second, people might be lured in thinking they are safe.

ffries commented 7 years ago

Now about master key:

Using the Proton "visionary" account, you can decrypt messages from an account. This is done encrypting messages with the account public key. This is called GPG double encryption. When a message is encrypted twice with two different public keys, it can be decrypted by either private key.

A master key would be a secret master key for ProtonMail and when needed, you can use it to double or tripple encrypt messages, so that messages can read by you. With a central service relying on a server, we cannot be 100% sure that there is no master key.

In history, crypto always failed when users thought they were protected. IMHO, in today's world, ProtonMail does not bring me more protection than the Enigma system.

bartbutler commented 7 years ago

Re: visionary account, it's not a master key, it's a key escrow system. So, you aren't encrypting messages with multiple keys, we are encrypting with one key, and the organization keeps a copy.

Re: everything else, nothing is 100% secure, and device security is an issue and not part of our threat model. We make no claims about hiding communications from governments who want to read your stuff. That said, we do think widespread PGP encryption use will foil mass surveillance, which we think is desirable and necessary. I'd rather force three-letter agencies to hack specific target devices than record everything from everyone all the time.

ffries commented 7 years ago

I don't agree with your view on "three-letter agencies". Ypu probably mean "FBI", "CIA", "MI6", etc ... This is a little bit far-fetched, let me try to explain why:

Democracy is actually challenged by terrorism. The challenge is to allow, included but not limited to, women to live freely, children to go to school, people to listen to music, journalists to be able to write articles and more generally individuals to discuss freely and be able to elect representatives. Very basic human rights are being challenged.

Therefore, there is a global understanding that some communications may be read. You refer as global surveillance. It is rather targeted at evil persons. And those "three-letter" agencies are actually defending freedom.

To get back to the point, your crypto system is broken, by design. I will not comment further and unregister from ProtonMail. I've tested enough from it.

ffries commented 7 years ago

I unregistered from Protonmail, just mark this message as "fixed". I read enough from your crypto model and you will not listen and we don't agree on basic principles.

Super-Baleine commented 7 years ago

Hey, @kellogsgit your private key is protected thanks to your passphrase and encrypted with AES thanks to the passphrase too you typed (am I right? @bartbutler ?) and that, before being sent to the servers. And while you're registering, your keys are generated at the client-side (within your web browser/mobile), and once generated, the private key is encrypted with AES. Once encrypted, the private key is stored at the servers side, but the thing's that your passphrase isn't sent, therefore nothing will get leaked.

They already talked about their security model at the MiXiT conference, take a look at their Twitter, it might be some information in it.

Also, how do you wanna access your mailbox from anywhere if you don't store the keys?

That's impossible to create a key that can decrypt other keys... Well, it might be possible if you were a member of the Illuminati conspiracy :)

bartbutler commented 7 years ago

You are correct @Super-Baleine.

Re: @kellogsgit Terrorism isn't an existential threat to democracy unless we allow it to be--it's a potential pretext to kill democracy to 'protect' us. I am not at all claiming that 3-letter agencies are evil, but government access to all communications from everyone is just as much if not more a threat to democracy, not because the people with that power will abuse it, but because they could. I'm much more comfortable with targeted hacking, which is very much within those agencies' capabilities.

That said, there are other reasons to encrypt your stuff, including criminals and identity theft. I would not assume ProtonMail will hide you from targeted government surveillance, as it very likely won't.

As for the rest of your comments, rather than that showing ProtonMail is 'broken by design', what they actually demonstrate is your misunderstandings about how ProtonMail, and GPG for that matter, work. I have tried to correct these misunderstandings, and for my trouble been accused of 'not listening'. I am listening, it's just that what I hear does not make sense.

Super-Baleine commented 7 years ago

It'd be even easier to understand if you're told that GPG basically is the program (such as GnuPG), and PGP is a standard.

@kellogsgit I recommend you to check that out x)

firegnome commented 6 years ago

What speaks against hashing the password on the client side and transmit only the hash. If the hash is equivalent to the one stored on the server, the server will transmit the encrypted private key and the client decrypts it with his password? I know this doesn't increase the security and has the consequence that the server needs to give away the salt and the hashing cycles to the client even if he is not authenticated.

Is the problem with the salt the main reason why this isn't a good approach? Or do you need to have the ability to decrypt the messages because of the law in switzerland, for example for emails related to terrorism?

But i totally agree with @bartbutler your solution is democracy compatible and i'm not questioning your solution. ;) But i'm wondering if there is a way where the server side is not able to break the encryption (without bruteforce).

Super-Baleine commented 6 years ago

@firegnome hey, your passphrase is derived to get the KEK (key encryption key), to be derived it requires a salt (with PBKDF2 for instance). This salt doesn't requires to be hidden. See key derivation for more details about this part.

Once the KEK obtained from the passphrase, you generate a nonce (an IV and some auth data if GCM). This generated nonce doesn't need to be hidden. Once the nonce generated, you use it with the KEK to encrypt the private RSA key of the user.

EDIT: If they're not using GCM, then they'd need to authenticate the encrypted content. (HMAC)

Once encrypted, you need to encapsulate the the encrypted private key with the nonce and the salt to re-proceed to the same key derivation on the next time. you send: packet=(encryptedPKA||salt||nonce). As you might see, the passphrase (I think you meant passphrase when you talked about password) isn't sent. So your private key is encrypted and it doesn't remain any other solution but bruteforce to get PKA (PKA means Private Key A, which means private RSA key in this case).

So, this is the simplest way to do, and probably one of the most secure solutions so far (plural because there are stronger key derivation algorithms than PBDF2 nowadays). I think ProtonMail use this way.

Hashing the passphrase and sending it to the servers isn't secure enough.... Moreover, it'd break the concept of 'privacy' because ProtonMail would be able to see your messages... what's the point then?

Hope my explanations helped to understand the concept. :)

firegnome commented 6 years ago

Hashing the passphrase and sending it to the servers isn't secure enough.... Moreover, it'd break the concept of 'privacy' because ProtonMail would be able to see your messages... what's the point then?

From my understanding this isn't correct because the emails are stored encrypted by my RSA-Public-Key. My private key is stored encrypted by my plainetext password on the server side. So the server needs to implement the logic that he only returns my encrypted private key to me if he i am sending the correct hashed password.

This would look like this

Client sends: I'd like to authenticate for me@protonmail.com Server sends: Give me your hashed password with salt: blabla and cycles 2^12 with bcrypt. Client sends: bcrypt(salt+ plaintextpassword) Server sends: If the hash matches the one on the server: return encrypted RSA-Private-Key and the emails that were encryptet with the RAS-Public-Key.

Now the client can decrypt the symetric encrypted RSA-Private-Key with his plainetest password. With the RSA-Private-Key he can now decrypt the emails.

I think i don't need to say that the keys need to be generated on the client side and the password and the RSA-Private-Key shall never be transmited to the server, except for the encrypted RSA-Private-Key.

Super-Baleine commented 6 years ago

The encryption of messages isn't related to the management of the private RSA key itself. That's another topic to discuss on.

As I understand, a symmetric encryption key is generated, let's call it CEK. The CEK encrypts the message content and the eventual blobs (attachments). The CEK is encrypted under your public RSA key and under the public RSA key of the recipient.

To me, it doesn't exist such a pain in the ass implementation with hashes and server verifications... But perhaps you're right, I don't know. But keep in mind that here, the topic is the management of PKA, emails encryption is a totally different topic. EDIT2: aren't you trying to understand SRP when you talk of verifications and such?

EDIT:

My private key is stored encrypted by my plainetext password on the server side

Well, it's not directly encrypted under your password (btw, your password is used to authenticate the user itself, it's not used for cryptography stuffs) because your passphrase (and not password) is derived with a salt and so on... anyway.

Super-Baleine commented 6 years ago

Client sends: bcrypt(salt+ plaintextpassword)

No, the passphrase (and not password) isn't sent... :)

firegnome commented 6 years ago

From what i understand is @ffries questioning the security at server side, because there exists the possibility to decrypt the emails and @bartbutler seems to confirm this supposition.

Now my question is: When i interpreted this correctly, why isn't ProtonMail using a concept like i described?

Correct me if i understand something wrong ;)

Thank you for your answers @Super-Baleine !

Super-Baleine commented 6 years ago

Where did Bart confirm such a shitty supposition? :o

No, perhaps you've understood what @ffries said, but you don't understand the ProtonMail's crypto-system. Everything you need to know is described above... x)

Actually your pair of asymmetric keys is used to encrypt the CEK of the email and authenticate the message. the Private Key is used to decrypt a message, but also to sign it (sign = authenticate). The Public Key is used to encrypt and verify a signature, so in this case the message is the CEK. Such as msg=(CEK).

Again, that's my guess. Perhaps they don't proceed so, in their MiXiT conference they talked about the standard they use with PGP, but I'm unfortunately too lazy to double-check :/

Super-Baleine commented 6 years ago

if you're brave enough: https://vimeo.com/216747532

firegnome commented 6 years ago

Well it seems i did overread this. This is exaclty what i excpect ProtonMail to do to be secure. And yes you we're right @bartbutler didn't confirm this, they were talking about the connection to the server.

Your ProtonMail private key is stored encrypted on your browser using your mailbox password. It is sent to our servers in the encrypted form. The only time it is decrypted is when you correctly enter your mailbox password. Your ProtonMail private key is stored encrypted on your browser using your mailbox password. It is sent to our servers in the encrypted form. The only time it is decrypted is when you correctly enter your mailbox password.

Thank you for your time and sorry for the trouble! This conversation helped me a lot!

From what i see now the concept of ProtonMail looks secure! :)

Super-Baleine commented 6 years ago

no problem :)

solardiz commented 3 years ago

In 2017, @bartbutler wrote in here:

we use a slow password hash function to generate a hash, and encrypt the key with that to make offline brute-forcing slow and the encrypted key alone less sensitive

I thought of asking for a clarification, but ended up mostly figuring this out myself, so let me share for others lurking in here:

Historically (prior to the comment quoted above), ProtonMail's derivation of the encryption key wasn't slow (but then the OpenPGP implementation probably used its own slow "s2k" algorithm anyway?) - this can still be seen in code as hashPassword0 and hashPassword1 (the current is hashPassword3). These older versions are kept in the code perhaps to support old ProtonMail accounts (are they getting upgraded to new hashing upon login?) Also, the plaintext password (base64-encoded) stayed in browser storage, per this old blog post (from 2015): https://arno0x0x.wordpress.com/2015/09/16/end2end-encryption-protonmail/ (way out of date, only included as a historical reference).

Things changed with @bartbutler's commit d510b25e6158ad4e9f7872641b5b74224cb9b11f in 2016, which introduced src/app/services/passwords.js implementing "hash version 3" building upon bcrypt:

    function expandHash(str) {
        return openpgp.util.concatUint8Array([
            openpgp.crypto.hash.sha512(pmcrypto.binaryStringToArray(str + '\x00')),
            openpgp.crypto.hash.sha512(pmcrypto.binaryStringToArray(str + '\x01')),
            openpgp.crypto.hash.sha512(pmcrypto.binaryStringToArray(str + '\x02')),
            openpgp.crypto.hash.sha512(pmcrypto.binaryStringToArray(str + '\x03'))
        ]);
    }

    function computeKeyPassword(password, salt) {

        if (salt && salt.length) {
            salt = pmcrypto.binaryStringToArray(pmcrypto.decode_base64(salt));
            return bcrypt(password, "$2y$10$" + dcodeIO.bcrypt.encodeBase64(salt, 16)).then(function(hash) {
                // Remove bcrypt prefix and salt (first 29 characters)
                return hash.slice(29);
            });
        }
[...]
    }

[...]

    const hashPasswordVersion = {
        4: function(password, salt, modulus) {
            return hashPasswordVersion[3](password, salt, modulus);
        },

        3: function(password, salt, modulus) {
            salt = pmcrypto.binaryStringToArray(salt + "proton");
            // We use the latest version of bcrypt, 2y, with 2^10 rounds.
            return bcrypt(password, "$2y$10$" + dcodeIO.bcrypt.encodeBase64(salt, 16)).then(function(unexpandedHash) {
                return expandHash(unexpandedHash + pmcrypto.arrayToBinaryString(modulus));
            });
        },

I'm still confused about version 3 vs. 4 there (why two numbers for the same thing), and about direct use of bcrypt result in computeKeyPassword vs. having it passed through expandHash first.

Then this changed further in commit 0f505768e8e2496d448ee704c4e63407bbe0696b in 2019, where the above functionality got moved from WebClient into pm-srp (separate git repo here). In there, it's lib/passwords.js, which currently has:

/**
 * Expand a hash
 * @param {Uint8Array} input
 * @returns {Promise<Uint8Array>}
 */
export const expandHash = async (input) => {
    const promises = [];
    const arr = concatArrays([input, new Uint8Array([0])]);
    for (let i = 1; i <= 4; i++) {
        promises.push(SHA512(arr));
        arr[arr.length - 1] = i;
    }
    return concatArrays(await Promise.all(promises));
};

/**
 * Format a hash
 * @param {String} password
 * @param {String} salt
 * @param {Uint8Array} modulus
 * @returns {Promise<Uint8Array>}
 */
const formatHash = async (password, salt, modulus) => {
    const unexpandedHash = await bcrypt.hash(password, BCRYPT_PREFIX + salt);
    return expandHash(concatArrays([binaryStringToArray(unexpandedHash), modulus]));
};

/**
 * Hash password in version 3.
 * @param {String} password
 * @param {String} salt
 * @param {Uint8Array} modulus
 * @returns {Promise<Uint8Array>}
 */
const hashPassword3 = (password, salt, modulus) => {
    const saltBinary = binaryStringToArray(salt + 'proton');
    return formatHash(password, bcrypt.encodeBase64(saltBinary, 16), modulus);
};

I suppose it remained compatible with the 2017 code, although the code has changed.

Reviewing a memory dump of a Linux VM with Firefox that had been logged into ProtonMail for some days (and was also used for some other web browsing), I see several different (why?) private key blocks from "OpenPGP.js v4.10.8" (maybe some came from elsewhere? or does ProtonMail use multiple such keys at once?) and plenty of pieces of ProtonMail messages I had viewed or sent, in plaintext. I couldn't find the plaintext password (nor it base64-encoded), nor strings likely to be the bcrypt hash or a portion of it, although this last test is unreliable. Maybe only the result of expandHash reliably stays in memory? YMMV. There has to be something still in memory to decrypt one of those private keys, or an already decrypted key, but not necessarily something sufficient to re-login to ProtonMail.

I then tried relogging in to the account with a breakpoint set in Firefox debugger on the password hashing above. Without being authenticated yet, I could capture the account's bcrypt salt and modulus - this makes sense, as those are in fact needed on the client prior to authentication. I then tried searching the previously made memory dump above for the specific bcrypt salt string (22 chars) - not found.

I think the dump's encrypted(?) private keys plus the salt and modulus obtained when attempting to log in should allow for an offline attack on the password (process each candidate password with code similar to snippets above, then attempt to decrypt the private key with the result). That's fine.

I also tried to log in to a (presumably) non-existent account. It hit my breakpoint too, and there was a salt and modulus too. In fact, repeating that for the same non-existent account resulted in the same modulus (and same salt? not sure I checked that) as the first attempt for that account. This suggests sound engineering, where non-existent accounts appear indistinguishable (in this quick test of mine) from existing latest-version accounts. (I didn't double-check, though.)

I wish all of the above were documented by ProtonMail somewhere - or is it? I could only find high-level descriptions, not the level of detail I want. This public response to a paper is somewhat more detailed, but still isn't proper documentation and not detailed enough for me: https://protonmail.com/blog/cryptographic-architecture-response/