DEV Community

End-to-End Encrypted Chat with the Web Crypto API

Matheus Cardoso on October 08, 2020

When transmitting or storing user data, especially private conversations, it's essential to consider employing cryptographic techniques to ensure p...
Collapse
 
yoursunny profile image
Junxiao Shi • Edited

The usage of constant IV with AES-GCM completely breaks its security. With AES-GCM, the application must guarantee that the IV is never repeated. Otherwise, it's a catastrophic failure.

If you use AES-GCM, each direction needs a different key or use a different IV range, and the IV should include a counter portion that is incremented for each AES block, then the keys must be rotated once the counter reaches the maximum.

It's simpler to use AES-CBC with random IV, and send the IV together with each message.

Collapse
 
cardoso profile image
Matheus Cardoso • Edited

Hi! Thanks a lot for the feedback. I missed this information while doing my very superficial research about AES-GCM (I just saw it wasn't sensitive, but didn't see it had to change every time). I'm currently changing the tutorial's approach to this and removing the misleading info.

Edit: done

Collapse
 
yoursunny profile image
Junxiao Shi

Last time I designed a system using random IV with AES-GCM and it got rejected in security review. Crypto expert says IV must have three parts:

  • Sender identifier. Suppose two parties are using the same key, 1 bit should be used to identify the encrypting party.
  • Random bits, 64 bits minus sender identifier.
  • Counter bits, 32 bits. Start from zero, incremented for each AES block (not message). Key must be rotated when the counter reaches maximum.
Collapse
 
crimsonmed profile image
Médéric Burlet

totally agree I flipped when I read this:

It's not a sensitive parameter in basic end-to-end encryption and only changes in more advanced use cases.

Collapse
 
cardoso profile image
Matheus Cardoso • Edited

Thanks a lot for the feedback. I made wrong assumptions while reading something quickly. I'm fixing the tutorial :)

Edit: done

Collapse
 
yoursunny profile image
Junxiao Shi

How does the user trust that the server is not a Man In The Middle during key exchange? What if the server gives each party its own ECDH public key during key exchange, and then decrypt-reencrypt each message?

Collapse
 
shierve profile image
Sergi Canal

You could verify the public key fingerprint if you wanted to make sure. It is impossible as far as I know to be able to verify identity without each participant getting a certificate with their public key from some sort of CA.

Collapse
 
yoursunny profile image
Junxiao Shi

My buddy made an Android app that lets users scan each other's QR code that encodes the public key. Basically MITM is not preventable without an out of band channel.

Collapse
 
cardoso profile image
Matheus Cardoso • Edited

Thanks a lot for your comment! Although I think it's out of scope for this basic tutorial, I've had this same question before. I believe It's not something that can be solved cryptographically. You need some approach like handing out the public key physically by the person (much like the QR code approach you described in another comment), and/or at least using TOFU (Trust on First Use) which is the approach partially taken by Signal (they just display a warning): en.wikipedia.org/wiki/Trust_on_fir...

Collapse
 
shierve profile image
Sergi Canal

As Junxiao said, it is dangerous to imply that IV is not an important parameter when using GCM. When reusing the IV in GCM, if an attacker captures a few encrypted messages, since basically GCM is an xor of the plaintext and the keystream (and the keystream is the same when using the same iv), then it is trivial to implement an attack that gets the keystream and unencrypts all the captured messages. It is basically the same attack that you would use for reused keys in One Time Pad. I highly encourage you to edit that part, otherwise the article is very useful, thanks.

Collapse
 
cardoso profile image
Matheus Cardoso • Edited

Thanks a lot for the feedback! I'm changing the tutorial's approach to this.

Edit: done

Collapse
 
yoursunny profile image
Junxiao Shi

const privateKeyJwk = await window.crypto.subtle.exportKey(
"jwk",
keyPair.privateKey
);

Why do you generate the key as exportable and save the JWK?
It's more secure to use non-exportable private keys, and store the key object in IndexedDB.

Collapse
 
cardoso profile image
Matheus Cardoso

Thanks for your comment! I didn't want to touch on private key persistence methods in this tutorial. If I got into that, I'd also need to tell how to recover or rotate the key pair in case the persistent storage is lost. So it was better just to leave it like this and tell that the private key is sensitive.

Collapse
 
yoursunny profile image
Junxiao Shi

Then you should use non-exportable key and pass it around as a variable.

Thread Thread
 
cardoso profile image
Matheus Cardoso

I left it like that to allow more than one session per user, mostly for not making the test app annoying to use. But I'll add further notes to emphasize that it's not great practice. Thanks :)

Collapse
 
yoursunny profile image
Junxiao Shi

I see you are using ECDH shared secret as AES key. This is not advisable because the shared secret could have bias. It needs to pass through HKDF step.

Also, P-256 can only provide 128-bit strength, so that you should be using AES-128, not AES-256.

Collapse
 
cardoso profile image
Matheus Cardoso

Thank you! I'll try improving this part as well

Collapse
 
yoursunny profile image
Junxiao Shi

This is still wrong:

For every encryption operation, it must be random and different to ensure the strength of the encryption.

IV in AES-GCM must be unique but does not need to be random.
IV in AES-CBC must be random.

Collapse
 
cardoso profile image
Matheus Cardoso • Edited

Thanks for your feedback! I considered switching to CBC, but didn't find a strong enough reason, since the AES-GCM is safe for encrypting 2^32 times with a randomly generated IV using the CSPRNG provided by the Web Crypto API.

Edit: but yes, I removed the "must" and added further details. Thanks again :)